1 /*****************************************************************************
2 * subtitle.c: Demux for subtitle text files.
3 *****************************************************************************
4 * Copyright (C) 1999-2007 VLC authors and VideoLAN
6 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
7 * Derk-Jan Hartman <hartman at videolan dot org>
8 * Jean-Baptiste Kempf <jb@videolan.org>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
40 #include <vlc_demux.h>
41 #include <vlc_charset.h>
43 /*****************************************************************************
45 *****************************************************************************/
46 static int Open ( vlc_object_t
*p_this
);
47 static void Close( vlc_object_t
*p_this
);
49 #define SUB_TYPE_LONGTEXT \
50 N_("Force the subtiles format. Selecting \"auto\" means autodetection and should always work.")
51 #define SUB_DESCRIPTION_LONGTEXT \
52 N_("Override the default track description.")
54 static const char *const ppsz_sub_type
[] =
56 "auto", "microdvd", "subrip", "subviewer", "ssa1",
57 "ssa2-4", "ass", "vplayer", "sami", "dvdsubtitle", "mpl2",
58 "aqt", "pjs", "mpsub", "jacosub", "psb", "realtext", "dks",
63 set_shortname( N_("Subtitles"))
64 set_description( N_("Text subtitle parser") )
65 set_capability( "demux", 0 )
66 set_category( CAT_INPUT
)
67 set_subcategory( SUBCAT_INPUT_DEMUX
)
68 add_string( "sub-type", "auto", N_("Subtitle format"),
69 SUB_TYPE_LONGTEXT
, true )
70 change_string_list( ppsz_sub_type
, ppsz_sub_type
)
71 add_string( "sub-description", NULL
, N_("Subtitle description"),
72 SUB_DESCRIPTION_LONGTEXT
, true )
73 set_callbacks( Open
, Close
)
75 add_shortcut( "subtitle" )
78 /*****************************************************************************
80 *****************************************************************************/
83 SUB_TYPE_UNKNOWN
= -1,
91 SUB_TYPE_SUBVIEWER
, /* SUBVIEWER 2 */
92 SUB_TYPE_DVDSUBTITLE
, /* Mplayer calls it subviewer2 */
101 SUB_TYPE_SUBVIEW1
, /* SUBVIEWER 1 - mplayer calls it subrip09,
102 and Gnome subtitles SubViewer 1.0 */
104 SUB_TYPE_SCC
, /* Scenarist Closed Caption */
114 static int TextLoad( text_t
*, stream_t
*s
);
115 static void TextUnload( text_t
* );
127 enum subtitle_type_e i_type
;
128 vlc_tick_t i_microsecperframe
;
130 char *psz_header
; /* SSA */
137 int i_time_resolution
;
151 const char *psz_start
;
163 vlc_tick_t i_next_demux_date
;
175 subs_properties_t props
;
177 block_t
* (*pf_convert
)( const subtitle_t
* );
180 static int ParseMicroDvd ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
181 static int ParseSubRip ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
182 static int ParseSubViewer ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
183 static int ParseSSA ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
184 static int ParseVplayer ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
185 static int ParseSami ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
186 static int ParseDVDSubtitle( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
187 static int ParseMPL2 ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
188 static int ParseAQT ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
189 static int ParsePJS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
190 static int ParseMPSub ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
191 static int ParseJSS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
192 static int ParsePSB ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
193 static int ParseRealText ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
194 static int ParseDKS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
195 static int ParseSubViewer1 ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
196 static int ParseCommonSBV ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
197 static int ParseSCC ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
201 const char *psz_type_name
;
203 const char *psz_name
;
204 int (*pf_read
)( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
205 } sub_read_subtitle_function
[] =
207 { "microdvd", SUB_TYPE_MICRODVD
, "MicroDVD", ParseMicroDvd
},
208 { "subrip", SUB_TYPE_SUBRIP
, "SubRIP", ParseSubRip
},
209 { "subviewer", SUB_TYPE_SUBVIEWER
, "SubViewer", ParseSubViewer
},
210 { "ssa1", SUB_TYPE_SSA1
, "SSA-1", ParseSSA
},
211 { "ssa2-4", SUB_TYPE_SSA2_4
, "SSA-2/3/4", ParseSSA
},
212 { "ass", SUB_TYPE_ASS
, "SSA/ASS", ParseSSA
},
213 { "vplayer", SUB_TYPE_VPLAYER
, "VPlayer", ParseVplayer
},
214 { "sami", SUB_TYPE_SAMI
, "SAMI", ParseSami
},
215 { "dvdsubtitle",SUB_TYPE_DVDSUBTITLE
, "DVDSubtitle", ParseDVDSubtitle
},
216 { "mpl2", SUB_TYPE_MPL2
, "MPL2", ParseMPL2
},
217 { "aqt", SUB_TYPE_AQT
, "AQTitle", ParseAQT
},
218 { "pjs", SUB_TYPE_PJS
, "PhoenixSub", ParsePJS
},
219 { "mpsub", SUB_TYPE_MPSUB
, "MPSub", ParseMPSub
},
220 { "jacosub", SUB_TYPE_JACOSUB
, "JacoSub", ParseJSS
},
221 { "psb", SUB_TYPE_PSB
, "PowerDivx", ParsePSB
},
222 { "realtext", SUB_TYPE_RT
, "RealText", ParseRealText
},
223 { "dks", SUB_TYPE_DKS
, "DKS", ParseDKS
},
224 { "subviewer1", SUB_TYPE_SUBVIEW1
, "Subviewer 1", ParseSubViewer1
},
225 { "sbv", SUB_TYPE_SBV
, "SBV", ParseCommonSBV
},
226 { "scc", SUB_TYPE_SCC
, "SCC", ParseSCC
},
227 { NULL
, SUB_TYPE_UNKNOWN
, "Unknown", NULL
}
229 /* When adding support for more formats, be sure to add their file extension
230 * to src/input/subtitles.c to enable auto-detection.
233 static int Demux( demux_t
* );
234 static int Control( demux_t
*, int, va_list );
236 static void Fix( demux_t
* );
237 static char * get_language_from_filename( const char * );
239 /*****************************************************************************
240 * Decoder format output function
241 *****************************************************************************/
243 static block_t
*ToTextBlock( const subtitle_t
*p_subtitle
)
246 size_t i_len
= strlen( p_subtitle
->psz_text
) + 1;
248 if( i_len
<= 1 || !(p_block
= block_Alloc( i_len
)) )
251 memcpy( p_block
->p_buffer
, p_subtitle
->psz_text
, i_len
);
256 static block_t
*ToEIA608Block( const subtitle_t
*p_subtitle
)
259 const size_t i_len
= strlen( p_subtitle
->psz_text
);
260 const size_t i_block
= (1 + i_len
/ 5) * 3;
262 if( i_len
< 4 || !(p_block
= block_Alloc( i_block
)) )
265 p_block
->i_buffer
= 0;
267 char *saveptr
= NULL
;
268 char *psz_tok
= strtok_r( p_subtitle
->psz_text
, " ", &saveptr
);
271 sscanf( psz_tok
, "%2x%2x", &a
, &b
) == 2 &&
272 i_block
- p_block
->i_buffer
>= 3 )
274 uint8_t *p_data
= &p_block
->p_buffer
[p_block
->i_buffer
];
278 p_block
->i_buffer
+= 3;
279 psz_tok
= strtok_r( NULL
, " ", &saveptr
);
285 /*****************************************************************************
287 *****************************************************************************/
288 static int Open ( vlc_object_t
*p_this
)
290 demux_t
*p_demux
= (demux_t
*)p_this
;
295 int (*pf_read
)( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
297 if( !p_demux
->obj
.force
)
299 msg_Dbg( p_demux
, "subtitle demux discarded" );
303 p_demux
->pf_demux
= Demux
;
304 p_demux
->pf_control
= Control
;
305 p_demux
->p_sys
= p_sys
= malloc( sizeof( demux_sys_t
) );
309 p_sys
->b_slave
= false;
310 p_sys
->b_first_time
= true;
311 p_sys
->i_next_demux_date
= 0;
314 p_sys
->pf_convert
= ToTextBlock
;
316 p_sys
->subtitles
.i_current
= 0;
317 p_sys
->subtitles
.i_count
= 0;
318 p_sys
->subtitles
.p_array
= NULL
;
320 p_sys
->props
.psz_header
= NULL
;
321 p_sys
->props
.i_microsecperframe
= VLC_TICK_FROM_MS(40);
322 p_sys
->props
.jss
.b_inited
= false;
323 p_sys
->props
.mpsub
.b_inited
= false;
324 p_sys
->props
.sami
.psz_start
= NULL
;
327 f_fps
= var_CreateGetFloat( p_demux
, "sub-original-fps" );
330 p_sys
->props
.i_microsecperframe
= llroundf( (float)CLOCK_FREQ
/ f_fps
);
331 msg_Dbg( p_demux
, "Override subtitle fps %f", (double) f_fps
);
334 /* Get or probe the type */
335 p_sys
->props
.i_type
= SUB_TYPE_UNKNOWN
;
336 psz_type
= var_CreateGetString( p_demux
, "sub-type" );
337 if( psz_type
&& *psz_type
)
339 for( int i
= 0; ; i
++ )
341 if( sub_read_subtitle_function
[i
].psz_type_name
== NULL
)
344 if( !strcmp( sub_read_subtitle_function
[i
].psz_type_name
,
347 p_sys
->props
.i_type
= sub_read_subtitle_function
[i
].i_type
;
355 const uint64_t i_start_pos
= vlc_stream_Tell( p_demux
->s
);
359 const uint8_t *p_peek
;
360 if( vlc_stream_Peek( p_demux
->s
, &p_peek
, 16 ) < 16 )
373 const char *psz_bom
= NULL
;
376 /* Detect Unicode while skipping the UTF-8 Byte Order Mark */
377 if( !memcmp( p_peek
, "\xEF\xBB\xBF", 3 ) )
382 else if( !memcmp( p_peek
, "\xFF\xFE", 2 ) )
385 psz_bom
= "UTF-16LE";
388 else if( !memcmp( p_peek
, "\xFE\xFF", 2 ) )
391 psz_bom
= "UTF-16BE";
396 msg_Dbg( p_demux
, "detected %s Byte Order Mark", psz_bom
);
398 i_peek
= vlc_stream_Peek( p_demux
->s
, &p_peek
, i_peek
);
399 if( unlikely(i_peek
< 16) )
405 stream_t
*p_probestream
= NULL
;
406 if( e_bom
!= UTF8BOM
&& e_bom
!= NOBOM
)
410 char *p_outbuf
= FromCharset( psz_bom
, p_peek
, i_peek
);
411 if( p_outbuf
!= NULL
)
412 p_probestream
= vlc_stream_MemoryNew( p_demux
, (uint8_t *)p_outbuf
,
414 false ); /* free p_outbuf on release */
419 const size_t i_skip
= (e_bom
== UTF8BOM
) ? 3 : 0;
420 p_probestream
= vlc_stream_MemoryNew( p_demux
, (uint8_t *) &p_peek
[i_skip
],
421 i_peek
- i_skip
, true );
424 if( p_probestream
== NULL
)
430 /* Probe if unknown type */
431 if( p_sys
->props
.i_type
== SUB_TYPE_UNKNOWN
)
436 msg_Dbg( p_demux
, "autodetecting subtitle format" );
437 for( i_try
= 0; i_try
< 256; i_try
++ )
442 if( (s
= vlc_stream_ReadLine( p_probestream
) ) == NULL
)
445 if( strcasestr( s
, "<SAMI>" ) )
447 p_sys
->props
.i_type
= SUB_TYPE_SAMI
;
450 else if( sscanf( s
, "{%d}{%d}", &i_dummy
, &i_dummy
) == 2 ||
451 sscanf( s
, "{%d}{}", &i_dummy
) == 1)
453 p_sys
->props
.i_type
= SUB_TYPE_MICRODVD
;
456 else if( sscanf( s
, "%d:%d:%d,%d --> %d:%d:%d,%d",
457 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
458 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
) == 8 ||
459 sscanf( s
, "%d:%d:%d --> %d:%d:%d,%d",
460 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
461 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
462 sscanf( s
, "%d:%d:%d,%d --> %d:%d:%d",
463 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
464 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
465 sscanf( s
, "%d:%d:%d.%d --> %d:%d:%d.%d",
466 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
467 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
) == 8 ||
468 sscanf( s
, "%d:%d:%d --> %d:%d:%d.%d",
469 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
470 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
471 sscanf( s
, "%d:%d:%d.%d --> %d:%d:%d",
472 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
473 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
474 sscanf( s
, "%d:%d:%d --> %d:%d:%d",
475 &i_dummy
,&i_dummy
,&i_dummy
,
476 &i_dummy
,&i_dummy
,&i_dummy
) == 6 )
478 p_sys
->props
.i_type
= SUB_TYPE_SUBRIP
;
481 else if( !strncasecmp( s
, "!: This is a Sub Station Alpha v1", 33 ) )
483 p_sys
->props
.i_type
= SUB_TYPE_SSA1
;
486 else if( !strncasecmp( s
, "ScriptType: v4.00+", 18 ) )
488 p_sys
->props
.i_type
= SUB_TYPE_ASS
;
491 else if( !strncasecmp( s
, "ScriptType: v4.00", 17 ) )
493 p_sys
->props
.i_type
= SUB_TYPE_SSA2_4
;
496 else if( !strncasecmp( s
, "Dialogue: Marked", 16 ) )
498 p_sys
->props
.i_type
= SUB_TYPE_SSA2_4
;
501 else if( !strncasecmp( s
, "Dialogue:", 9 ) )
503 p_sys
->props
.i_type
= SUB_TYPE_ASS
;
506 else if( strcasestr( s
, "[INFORMATION]" ) )
508 p_sys
->props
.i_type
= SUB_TYPE_SUBVIEWER
; /* I hope this will work */
511 else if( sscanf( s
, "%d:%d:%d.%d %d:%d:%d",
512 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
,
513 &i_dummy
, &i_dummy
, &i_dummy
) == 7 ||
514 sscanf( s
, "@%d @%d", &i_dummy
, &i_dummy
) == 2)
516 p_sys
->props
.i_type
= SUB_TYPE_JACOSUB
;
519 else if( sscanf( s
, "%d:%d:%d.%d,%d:%d:%d.%d",
520 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
,
521 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
) == 8 )
523 p_sys
->props
.i_type
= SUB_TYPE_SBV
;
526 else if( sscanf( s
, "%d:%d:%d:", &i_dummy
, &i_dummy
, &i_dummy
) == 3 ||
527 sscanf( s
, "%d:%d:%d ", &i_dummy
, &i_dummy
, &i_dummy
) == 3 )
529 p_sys
->props
.i_type
= SUB_TYPE_VPLAYER
;
532 else if( sscanf( s
, "{T %d:%d:%d:%d", &i_dummy
, &i_dummy
,
533 &i_dummy
, &i_dummy
) == 4 )
535 p_sys
->props
.i_type
= SUB_TYPE_DVDSUBTITLE
;
538 else if( sscanf( s
, "[%d:%d:%d]%c",
539 &i_dummy
, &i_dummy
, &i_dummy
, &p_dummy
) == 4 )
541 p_sys
->props
.i_type
= SUB_TYPE_DKS
;
544 else if( strstr( s
, "*** START SCRIPT" ) )
546 p_sys
->props
.i_type
= SUB_TYPE_SUBVIEW1
;
549 else if( sscanf( s
, "[%d][%d]", &i_dummy
, &i_dummy
) == 2 ||
550 sscanf( s
, "[%d][]", &i_dummy
) == 1)
552 p_sys
->props
.i_type
= SUB_TYPE_MPL2
;
555 else if( sscanf (s
, "FORMAT=%d", &i_dummy
) == 1 ||
556 ( sscanf (s
, "FORMAT=TIM%c", &p_dummy
) == 1
559 p_sys
->props
.i_type
= SUB_TYPE_MPSUB
;
562 else if( sscanf( s
, "-->> %d", &i_dummy
) == 1 )
564 p_sys
->props
.i_type
= SUB_TYPE_AQT
;
567 else if( sscanf( s
, "%d,%d,", &i_dummy
, &i_dummy
) == 2 )
569 p_sys
->props
.i_type
= SUB_TYPE_PJS
;
572 else if( sscanf( s
, "{%d:%d:%d}",
573 &i_dummy
, &i_dummy
, &i_dummy
) == 3 )
575 p_sys
->props
.i_type
= SUB_TYPE_PSB
;
578 else if( strcasestr( s
, "<time" ) )
580 p_sys
->props
.i_type
= SUB_TYPE_RT
;
583 else if( !strncasecmp( s
, "WEBVTT",6 ) )
588 else if( !strncasecmp( s
, "Scenarist_SCC V1.0", 18 ) )
590 p_sys
->props
.i_type
= SUB_TYPE_SCC
;
591 p_sys
->pf_convert
= ToEIA608Block
;
602 vlc_stream_Delete( p_probestream
);
604 /* Quit on unknown subtitles */
605 if( p_sys
->props
.i_type
== SUB_TYPE_UNKNOWN
)
608 /* Ensure it will work with non seekable streams */
609 assert( i_start_pos
== vlc_stream_Tell( p_demux
->s
) );
611 msg_Warn( p_demux
, "failed to recognize subtitle type" );
616 for( int i
= 0; ; i
++ )
618 if( sub_read_subtitle_function
[i
].i_type
== p_sys
->props
.i_type
)
620 msg_Dbg( p_demux
, "detected %s format",
621 sub_read_subtitle_function
[i
].psz_name
);
622 pf_read
= sub_read_subtitle_function
[i
].pf_read
;
627 msg_Dbg( p_demux
, "loading all subtitles..." );
629 if( e_bom
== UTF8BOM
&& /* skip BOM */
630 vlc_stream_Read( p_demux
->s
, NULL
, 3 ) != 3 )
636 /* Load the whole file */
638 TextLoad( &txtlines
, p_demux
->s
);
641 for( size_t i_max
= 0; i_max
< SIZE_MAX
- 500 * sizeof(subtitle_t
); )
643 if( p_sys
->subtitles
.i_count
>= i_max
)
646 subtitle_t
*p_realloc
= realloc( p_sys
->subtitles
.p_array
, sizeof(subtitle_t
) * i_max
);
647 if( p_realloc
== NULL
)
649 TextUnload( &txtlines
);
653 p_sys
->subtitles
.p_array
= p_realloc
;
656 if( pf_read( VLC_OBJECT(p_demux
), &p_sys
->props
, &txtlines
,
657 &p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_count
],
658 p_sys
->subtitles
.i_count
) )
661 p_sys
->subtitles
.i_count
++;
664 TextUnload( &txtlines
);
666 msg_Dbg(p_demux
, "loaded %zu subtitles", p_sys
->subtitles
.i_count
);
668 /* *** add subtitle ES *** */
669 if( p_sys
->props
.i_type
== SUB_TYPE_SSA1
||
670 p_sys
->props
.i_type
== SUB_TYPE_SSA2_4
||
671 p_sys
->props
.i_type
== SUB_TYPE_ASS
)
674 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_SSA
);
676 else if( p_sys
->props
.i_type
== SUB_TYPE_SCC
)
678 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_CEA608
);
679 fmt
.subs
.cc
.i_reorder_depth
= -1;
682 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_SUBT
);
684 p_sys
->subtitles
.i_current
= 0;
686 if( p_sys
->subtitles
.i_count
> 0 )
687 p_sys
->i_length
= p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_count
-1].i_stop
;
689 /* Stupid language detection in the filename */
690 char * psz_language
= get_language_from_filename( p_demux
->psz_filepath
);
694 fmt
.psz_language
= psz_language
;
695 msg_Dbg( p_demux
, "detected language %s of subtitle: %s", psz_language
,
696 p_demux
->psz_location
);
699 char *psz_description
= var_InheritString( p_demux
, "sub-description" );
700 if( psz_description
&& *psz_description
)
701 fmt
.psz_description
= psz_description
;
703 free( psz_description
);
704 if( p_sys
->props
.psz_header
!= NULL
&&
705 (fmt
.p_extra
= strdup( p_sys
->props
.psz_header
)) )
707 fmt
.i_extra
= strlen( p_sys
->props
.psz_header
) + 1;
711 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
712 es_format_Clean( &fmt
);
713 if( p_sys
->es
== NULL
)
722 /*****************************************************************************
723 * Close: Close subtitle demux
724 *****************************************************************************/
725 static void Close( vlc_object_t
*p_this
)
727 demux_t
*p_demux
= (demux_t
*)p_this
;
728 demux_sys_t
*p_sys
= p_demux
->p_sys
;
730 for( size_t i
= 0; i
< p_sys
->subtitles
.i_count
; i
++ )
731 free( p_sys
->subtitles
.p_array
[i
].psz_text
);
732 free( p_sys
->subtitles
.p_array
);
733 free( p_sys
->props
.psz_header
);
739 ResetCurrentIndex( demux_t
*p_demux
)
741 demux_sys_t
*p_sys
= p_demux
->p_sys
;
742 for( size_t i
= 0; i
< p_sys
->subtitles
.i_count
; i
++ )
744 if( p_sys
->subtitles
.p_array
[i
].i_start
* p_sys
->f_rate
>
745 p_sys
->i_next_demux_date
&& i
> 0 )
747 p_sys
->subtitles
.i_current
= i
;
751 /*****************************************************************************
753 *****************************************************************************/
754 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
756 demux_sys_t
*p_sys
= p_demux
->p_sys
;
762 *va_arg( args
, bool * ) = true;
765 case DEMUX_GET_LENGTH
:
766 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_length
;
770 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_next_demux_date
;
775 p_sys
->b_first_time
= true;
776 p_sys
->i_next_demux_date
= va_arg( args
, vlc_tick_t
);
777 ResetCurrentIndex( p_demux
);
781 case DEMUX_GET_POSITION
:
782 pf
= va_arg( args
, double * );
783 if( p_sys
->subtitles
.i_current
>= p_sys
->subtitles
.i_count
)
787 else if( p_sys
->subtitles
.i_count
> 0 && p_sys
->i_length
)
789 *pf
= p_sys
->i_next_demux_date
;
790 *pf
/= p_sys
->i_length
;
798 case DEMUX_SET_POSITION
:
799 f
= va_arg( args
, double );
800 if( p_sys
->subtitles
.i_count
&& p_sys
->i_length
)
802 vlc_tick_t i64
= VLC_TICK_0
+ f
* p_sys
->i_length
;
803 return demux_Control( p_demux
, DEMUX_SET_TIME
, i64
);
807 case DEMUX_CAN_CONTROL_RATE
:
808 *va_arg( args
, bool * ) = true;
811 p_sys
->f_rate
= *va_arg( args
, float * );
812 ResetCurrentIndex( p_demux
);
814 case DEMUX_SET_NEXT_DEMUX_TIME
:
815 p_sys
->b_slave
= true;
816 p_sys
->i_next_demux_date
= va_arg( args
, vlc_tick_t
) - VLC_TICK_0
;
819 case DEMUX_CAN_PAUSE
:
820 case DEMUX_SET_PAUSE_STATE
:
821 case DEMUX_CAN_CONTROL_PACE
:
822 return demux_vaControlHelper( p_demux
->s
, 0, -1, 0, 1, i_query
, args
);
824 case DEMUX_GET_PTS_DELAY
:
827 case DEMUX_GET_ATTACHMENTS
:
828 case DEMUX_GET_TITLE_INFO
:
829 case DEMUX_HAS_UNSUPPORTED_META
:
830 case DEMUX_CAN_RECORD
:
838 /*****************************************************************************
839 * Demux: Send subtitle to decoder
840 *****************************************************************************/
841 static int Demux( demux_t
*p_demux
)
843 demux_sys_t
*p_sys
= p_demux
->p_sys
;
845 vlc_tick_t i_barrier
= p_sys
->i_next_demux_date
;
847 while( p_sys
->subtitles
.i_current
< p_sys
->subtitles
.i_count
&&
848 ( p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_current
].i_start
*
849 p_sys
->f_rate
) <= i_barrier
)
851 const subtitle_t
*p_subtitle
= &p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_current
];
853 if ( !p_sys
->b_slave
&& p_sys
->b_first_time
)
855 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
856 p_sys
->b_first_time
= false;
859 if( p_subtitle
->i_start
>= 0 )
861 block_t
*p_block
= p_sys
->pf_convert( p_subtitle
);
865 p_block
->i_pts
= VLC_TICK_0
+ p_subtitle
->i_start
* p_sys
->f_rate
;
866 if( p_subtitle
->i_stop
>= 0 && p_subtitle
->i_stop
>= p_subtitle
->i_start
)
867 p_block
->i_length
= (p_subtitle
->i_stop
- p_subtitle
->i_start
) * p_sys
->f_rate
;
869 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
873 p_sys
->subtitles
.i_current
++;
876 if ( !p_sys
->b_slave
)
878 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
879 p_sys
->i_next_demux_date
+= VLC_TICK_FROM_MS(125);
882 if( p_sys
->subtitles
.i_current
>= p_sys
->subtitles
.i_count
)
883 return VLC_DEMUXER_EOF
;
885 return VLC_DEMUXER_SUCCESS
;
889 static int subtitle_cmp( const void *first
, const void *second
)
891 vlc_tick_t result
= ((subtitle_t
*)(first
))->i_start
- ((subtitle_t
*)(second
))->i_start
;
892 /* Return -1, 0 ,1, and not directly subtraction
893 * as result can be > INT_MAX */
894 return result
== 0 ? 0 : result
> 0 ? 1 : -1;
896 /*****************************************************************************
897 * Fix: fix time stamp and order of subtitle
898 *****************************************************************************/
899 static void Fix( demux_t
*p_demux
)
901 demux_sys_t
*p_sys
= p_demux
->p_sys
;
903 /* *** fix order (to be sure...) *** */
904 qsort( p_sys
->subtitles
.p_array
, p_sys
->subtitles
.i_count
, sizeof( p_sys
->subtitles
.p_array
[0] ), subtitle_cmp
);
907 static int TextLoad( text_t
*txt
, stream_t
*s
)
913 txt
->i_line_count
= 0;
915 txt
->line
= calloc( i_line_max
, sizeof( char * ) );
919 /* load the complete file */
922 char *psz
= vlc_stream_ReadLine( s
);
927 txt
->line
[txt
->i_line_count
] = psz
;
928 if( txt
->i_line_count
+ 1 >= i_line_max
)
931 char **p_realloc
= realloc( txt
->line
, i_line_max
* sizeof( char * ) );
932 if( p_realloc
== NULL
)
934 txt
->line
= p_realloc
;
939 if( txt
->i_line_count
== 0 )
947 static void TextUnload( text_t
*txt
)
949 if( txt
->i_line_count
)
951 for( size_t i
= 0; i
< txt
->i_line_count
; i
++ )
952 free( txt
->line
[i
] );
956 txt
->i_line_count
= 0;
959 static char *TextGetLine( text_t
*txt
)
961 if( txt
->i_line
>= txt
->i_line_count
)
964 return txt
->line
[txt
->i_line
++];
966 static void TextPreviousLine( text_t
*txt
)
968 if( txt
->i_line
> 0 )
972 /*****************************************************************************
973 * Specific Subtitle function
974 *****************************************************************************/
977 * {n1}{n2}Line1|Line2|Line3....
978 * where n1 and n2 are the video frame number (n2 can be empty)
980 static int ParseMicroDvd( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
981 text_t
*txt
, subtitle_t
*p_subtitle
,
992 const char *s
= TextGetLine( txt
);
996 psz_text
= malloc( strlen(s
) + 1 );
1002 if( sscanf( s
, "{%d}{}%[^\r\n]", &i_start
, psz_text
) == 2 ||
1003 sscanf( s
, "{%d}{%d}%[^\r\n]", &i_start
, &i_stop
, psz_text
) == 3)
1005 if( i_start
!= 1 || i_stop
!= 1 )
1008 /* We found a possible setting of the framerate "{1}{1}23.976" */
1009 /* Check if it's usable, and if the sub-original-fps is not set */
1010 float f_fps
= us_strtof( psz_text
, NULL
);
1011 if( f_fps
> 0.f
&& var_GetFloat( p_obj
, "sub-original-fps" ) <= 0.f
)
1012 p_props
->i_microsecperframe
= llroundf((float)CLOCK_FREQ
/ f_fps
);
1017 /* replace | by \n */
1018 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
1020 if( psz_text
[i
] == '|' )
1025 p_subtitle
->i_start
= i_start
* p_props
->i_microsecperframe
;
1026 p_subtitle
->i_stop
= i_stop
>= 0 ? (i_stop
* p_props
->i_microsecperframe
) : -1;
1027 p_subtitle
->psz_text
= psz_text
;
1031 /* ParseSubRipSubViewer
1034 * h1:m1:s1,d1 --> h2:m2:s2,d2
1039 * Format SubViewer v1/v2
1040 * h1:m1:s1.d1,h2:m2:s2.d2
1045 * We ignore line number for SubRip
1047 static int ParseSubRipSubViewer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1048 text_t
*txt
, subtitle_t
*p_subtitle
,
1049 int (* pf_parse_timing
)(subtitle_t
*, const char *),
1053 VLC_UNUSED(p_props
);
1058 const char *s
= TextGetLine( txt
);
1061 return VLC_EGENERIC
;
1063 if( pf_parse_timing( p_subtitle
, s
) == VLC_SUCCESS
&&
1064 p_subtitle
->i_start
< p_subtitle
->i_stop
)
1070 /* Now read text until an empty line */
1071 psz_text
= strdup("");
1077 const char *s
= TextGetLine( txt
);
1081 i_len
= s
? strlen( s
) : 0;
1084 p_subtitle
->psz_text
= psz_text
;
1088 i_old
= strlen( psz_text
);
1089 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1094 strcat( psz_text
, s
);
1095 strcat( psz_text
, "\n" );
1097 /* replace [br] by \n */
1102 while( ( p
= strstr( psz_text
, "[br]" ) ) )
1105 memmove( p
, &p
[3], strlen(&p
[3])+1 );
1111 /* subtitle_ParseSubRipTimingValue
1112 * Parses SubRip timing value.
1114 static int subtitle_ParseSubRipTimingValue(vlc_tick_t
*timing_value
,
1117 int h1
, m1
, s1
, d1
= 0;
1119 if ( sscanf( s
, "%d:%d:%d,%d",
1120 &h1
, &m1
, &s1
, &d1
) == 4 ||
1121 sscanf( s
, "%d:%d:%d.%d",
1122 &h1
, &m1
, &s1
, &d1
) == 4 ||
1123 sscanf( s
, "%d:%d:%d",
1124 &h1
, &m1
, &s1
) == 3 )
1126 (*timing_value
) = vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1127 VLC_TICK_FROM_MS( d1
);
1132 return VLC_EGENERIC
;
1135 /* subtitle_ParseSubRipTiming
1136 * Parses SubRip timing.
1138 static int subtitle_ParseSubRipTiming( subtitle_t
*p_subtitle
,
1141 int i_result
= VLC_EGENERIC
;
1142 char *psz_start
, *psz_stop
;
1143 psz_start
= malloc( strlen(s
) + 1 );
1144 psz_stop
= malloc( strlen(s
) + 1 );
1146 if( sscanf( s
, "%s --> %s", psz_start
, psz_stop
) == 2 &&
1147 subtitle_ParseSubRipTimingValue( &p_subtitle
->i_start
, psz_start
) == VLC_SUCCESS
&&
1148 subtitle_ParseSubRipTimingValue( &p_subtitle
->i_stop
, psz_stop
) == VLC_SUCCESS
)
1150 i_result
= VLC_SUCCESS
;
1160 static int ParseSubRip( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1161 text_t
*txt
, subtitle_t
*p_subtitle
,
1164 VLC_UNUSED( i_idx
);
1165 return ParseSubRipSubViewer( p_obj
, p_props
, txt
, p_subtitle
,
1166 &subtitle_ParseSubRipTiming
,
1170 /* subtitle_ParseSubViewerTiming
1171 * Parses SubViewer timing.
1173 static int subtitle_ParseSubViewerTiming( subtitle_t
*p_subtitle
,
1176 int h1
, m1
, s1
, d1
, h2
, m2
, s2
, d2
;
1178 if( sscanf( s
, "%d:%d:%d.%d,%d:%d:%d.%d",
1179 &h1
, &m1
, &s1
, &d1
, &h2
, &m2
, &s2
, &d2
) == 8 )
1181 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1182 VLC_TICK_FROM_MS( d1
);
1184 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
1185 VLC_TICK_FROM_MS( d2
);
1188 return VLC_EGENERIC
;
1193 static int ParseSubViewer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1194 text_t
*txt
, subtitle_t
*p_subtitle
,
1197 VLC_UNUSED( i_idx
);
1199 return ParseSubRipSubViewer( p_obj
, p_props
, txt
, p_subtitle
,
1200 &subtitle_ParseSubViewerTiming
,
1206 static int ParseSSA( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1207 text_t
*txt
, subtitle_t
*p_subtitle
,
1211 size_t header_len
= 0;
1215 const char *s
= TextGetLine( txt
);
1216 int h1
, m1
, s1
, c1
, h2
, m2
, s2
, c2
;
1217 char *psz_text
, *psz_temp
;
1221 return VLC_EGENERIC
;
1223 /* We expect (SSA2-4):
1224 * Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1225 * Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1227 * 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.
1231 * Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1232 * Dialogue: Layer#,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1235 /* The output text is - at least, not removing numbers - 18 chars shorter than the input text. */
1236 psz_text
= malloc( strlen(s
) );
1241 "Dialogue: %15[^,],%d:%d:%d.%d,%d:%d:%d.%d,%[^\r\n]",
1247 /* The dec expects: ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text */
1248 /* (Layer comes from ASS specs ... it's empty for SSA.) */
1249 if( p_props
->i_type
== SUB_TYPE_SSA1
)
1251 /* SSA1 has only 8 commas before the text starts, not 9 */
1252 memmove( &psz_text
[1], psz_text
, strlen(psz_text
)+1 );
1257 int i_layer
= ( p_props
->i_type
== SUB_TYPE_ASS
) ? atoi( temp
) : 0;
1259 /* ReadOrder, Layer, %s(rest of fields) */
1260 if( asprintf( &psz_temp
, "%zu,%d,%s", i_idx
, i_layer
, psz_text
) == -1 )
1267 psz_text
= psz_temp
;
1270 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1271 VLC_TICK_FROM_MS( c1
* 10 );
1272 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
1273 VLC_TICK_FROM_MS( c2
* 10 );
1274 p_subtitle
->psz_text
= psz_text
;
1279 /* All the other stuff we add to the header field */
1280 if( header_len
== 0 && p_props
->psz_header
)
1281 header_len
= strlen( p_props
->psz_header
);
1283 size_t s_len
= strlen( s
);
1284 p_props
->psz_header
= realloc_or_free( p_props
->psz_header
, header_len
+ s_len
+ 2 );
1285 if( !p_props
->psz_header
)
1287 snprintf( p_props
->psz_header
+ header_len
, s_len
+ 2, "%s\n", s
);
1288 header_len
+= s_len
+ 1;
1294 * h:m:s:Line1|Line2|Line3....
1296 * h:m:s Line1|Line2|Line3....
1298 static int ParseVplayer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1299 text_t
*txt
, subtitle_t
*p_subtitle
,
1303 VLC_UNUSED(p_props
);
1304 VLC_UNUSED( i_idx
);
1309 const char *s
= TextGetLine( txt
);
1313 return VLC_EGENERIC
;
1315 psz_text
= malloc( strlen( s
) + 1 );
1319 if( sscanf( s
, "%d:%d:%d%*c%[^\r\n]",
1320 &h1
, &m1
, &s1
, psz_text
) == 4 )
1322 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
1323 p_subtitle
->i_stop
= -1;
1329 /* replace | by \n */
1330 for( size_t i
= 0; psz_text
[i
] != '\0'; i
++ )
1332 if( psz_text
[i
] == '|' )
1335 p_subtitle
->psz_text
= psz_text
;
1341 static const char *ParseSamiSearch( text_t
*txt
,
1342 const char *psz_start
, const char *psz_str
)
1344 if( psz_start
&& strcasestr( psz_start
, psz_str
) )
1346 const char *s
= strcasestr( psz_start
, psz_str
);
1347 return &s
[strlen( psz_str
)];
1352 const char *p
= TextGetLine( txt
);
1356 const char *s
= strcasestr( p
, psz_str
);
1358 return &s
[strlen( psz_str
)];
1361 static int ParseSami( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1362 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1365 VLC_UNUSED(p_props
);
1366 VLC_UNUSED( i_idx
);
1370 unsigned int i_text
;
1371 char text
[8192]; /* Arbitrary but should be long enough */
1373 /* search "Start=" */
1374 s
= ParseSamiSearch( txt
, p_props
->sami
.psz_start
, "Start=" );
1375 p_props
->sami
.psz_start
= NULL
;
1377 return VLC_EGENERIC
;
1379 /* get start value */
1381 i_start
= strtol( s
, &psz_end
, 0 );
1385 if( !( s
= ParseSamiSearch( txt
, s
, "<P" ) ) )
1386 return VLC_EGENERIC
;
1389 if( !( s
= ParseSamiSearch( txt
, s
, ">" ) ) )
1390 return VLC_EGENERIC
;
1394 /* now get all txt until a "Start=" line */
1398 /* Search non empty line */
1399 while( s
&& *s
== '\0' )
1400 s
= TextGetLine( txt
);
1406 if( !strncasecmp( s
, "<br", 3 ) )
1410 else if( strcasestr( s
, "Start=" ) )
1412 p_props
->sami
.psz_start
= s
;
1415 s
= ParseSamiSearch( txt
, s
, ">" );
1417 else if( !strncmp( s
, " ", 6 ) )
1422 else if( *s
== '\t' )
1432 if( c
!= '\0' && i_text
+1 < sizeof(text
) )
1435 text
[i_text
] = '\0';
1439 p_subtitle
->i_start
= VLC_TICK_FROM_MS(i_start
);
1440 p_subtitle
->i_stop
= -1;
1441 p_subtitle
->psz_text
= strdup( text
);
1453 * TODO it can have a header
1460 * LANG support would be cool
1461 * CODEPAGE is probably mandatory FIXME
1463 static int ParseDVDSubtitle(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1464 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1467 VLC_UNUSED(p_props
);
1468 VLC_UNUSED( i_idx
);
1473 const char *s
= TextGetLine( txt
);
1477 return VLC_EGENERIC
;
1481 &h1
, &m1
, &s1
, &c1
) == 4 )
1483 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1484 VLC_TICK_FROM_MS( c1
* 10 );
1485 p_subtitle
->i_stop
= -1;
1490 /* Now read text until a line containing "}" */
1491 psz_text
= strdup("");
1496 const char *s
= TextGetLine( txt
);
1503 return VLC_EGENERIC
;
1506 i_len
= strlen( s
);
1507 if( i_len
== 1 && s
[0] == '}')
1509 p_subtitle
->psz_text
= psz_text
;
1513 i_old
= strlen( psz_text
);
1514 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1517 strcat( psz_text
, s
);
1518 strcat( psz_text
, "\n" );
1524 * [n1][n2]Line1|Line2|Line3...
1525 * where n1 and n2 are the video frame number (n2 can be empty)
1527 static int ParseMPL2(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1528 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1531 VLC_UNUSED(p_props
);
1532 VLC_UNUSED( i_idx
);
1538 const char *s
= TextGetLine( txt
);
1543 return VLC_EGENERIC
;
1545 psz_text
= malloc( strlen(s
) + 1 );
1551 if( sscanf( s
, "[%d][] %[^\r\n]", &i_start
, psz_text
) == 2 ||
1552 sscanf( s
, "[%d][%d] %[^\r\n]", &i_start
, &i_stop
, psz_text
) == 3)
1554 p_subtitle
->i_start
= VLC_TICK_FROM_MS(i_start
* 100);
1555 p_subtitle
->i_stop
= i_stop
>= 0 ? VLC_TICK_FROM_MS(i_stop
* 100) : -1;
1561 for( i
= 0; psz_text
[i
] != '\0'; )
1563 /* replace | by \n */
1564 if( psz_text
[i
] == '|' )
1568 if( psz_text
[i
] == '/' && ( i
== 0 || psz_text
[i
-1] == '\n' ) )
1569 memmove( &psz_text
[i
], &psz_text
[i
+1], strlen(&psz_text
[i
+1])+1 );
1573 p_subtitle
->psz_text
= psz_text
;
1577 static int ParseAQT(vlc_object_t
*p_obj
, subs_properties_t
*p_props
, text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1580 VLC_UNUSED(p_props
);
1581 VLC_UNUSED( i_idx
);
1583 char *psz_text
= strdup( "" );
1585 int i_firstline
= 1;
1591 const char *s
= TextGetLine( txt
);
1596 return VLC_EGENERIC
;
1600 if( sscanf (s
, "-->> %d", &t
) == 1)
1602 /* Starting of a subtitle */
1605 p_subtitle
->i_start
= t
* p_props
->i_microsecperframe
;
1608 /* We have been too far: end of the subtitle, begin of next */
1611 p_subtitle
->i_stop
= t
* p_props
->i_microsecperframe
;
1618 i_old
= strlen( psz_text
) + 1;
1619 psz_text
= realloc_or_free( psz_text
, i_old
+ strlen( s
) + 1 );
1622 strcat( psz_text
, s
);
1623 strcat( psz_text
, "\n" );
1624 if( txt
->i_line
== txt
->i_line_count
)
1628 p_subtitle
->psz_text
= psz_text
;
1632 static int ParsePJS(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1633 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1636 VLC_UNUSED(p_props
);
1637 VLC_UNUSED( i_idx
);
1644 const char *s
= TextGetLine( txt
);
1648 return VLC_EGENERIC
;
1650 psz_text
= malloc( strlen(s
) + 1 );
1655 if( sscanf (s
, "%d,%d,\"%[^\n\r]", &t1
, &t2
, psz_text
) == 3 )
1657 /* 1/10th of second ? Frame based ? FIXME */
1658 p_subtitle
->i_start
= 10 * t1
;
1659 p_subtitle
->i_stop
= 10 * t2
;
1660 /* Remove latest " */
1661 psz_text
[ strlen(psz_text
) - 1 ] = '\0';
1668 /* replace | by \n */
1669 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
1671 if( psz_text
[i
] == '|' )
1675 p_subtitle
->psz_text
= psz_text
;
1676 msg_Dbg( p_obj
, "%s", psz_text
);
1680 static int ParseMPSub( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1681 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1683 VLC_UNUSED( i_idx
);
1685 char *psz_text
= strdup( "" );
1687 if( !p_props
->mpsub
.b_inited
)
1689 p_props
->mpsub
.f_total
= 0.0;
1690 p_props
->mpsub
.i_factor
= 0;
1692 p_props
->mpsub
.b_inited
= true;
1700 const char *s
= TextGetLine( txt
);
1704 return VLC_EGENERIC
;
1707 if( strstr( s
, "FORMAT" ) )
1709 if( sscanf (s
, "FORMAT=TIM%c", &p_dummy
) == 1 && p_dummy
== 'E')
1711 p_props
->mpsub
.i_factor
= 100;
1715 psz_temp
= malloc( strlen(s
) );
1722 if( sscanf( s
, "FORMAT=%[^\r\n]", psz_temp
) )
1724 float f_fps
= us_strtof( psz_temp
, NULL
);
1726 if( f_fps
> 0.f
&& var_GetFloat( p_obj
, "sub-original-fps" ) <= 0.f
)
1727 var_SetFloat( p_obj
, "sub-original-fps", f_fps
);
1729 p_props
->mpsub
.i_factor
= 1;
1737 float f1
= us_strtof( s
, &psz_temp
);
1740 float f2
= us_strtof( psz_temp
, NULL
);
1741 p_props
->mpsub
.f_total
+= f1
* p_props
->mpsub
.i_factor
;
1742 p_subtitle
->i_start
= llroundf(10000.f
* p_props
->mpsub
.f_total
);
1743 p_props
->mpsub
.f_total
+= f2
* p_props
->mpsub
.i_factor
;
1744 p_subtitle
->i_stop
= llroundf(10000.f
* p_props
->mpsub
.f_total
);
1751 const char *s
= TextGetLine( txt
);
1756 return VLC_EGENERIC
;
1759 size_t i_len
= strlen( s
);
1763 size_t i_old
= strlen( psz_text
);
1765 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1769 strcat( psz_text
, s
);
1770 strcat( psz_text
, "\n" );
1773 p_subtitle
->psz_text
= psz_text
;
1777 static int ParseJSS( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1778 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1780 VLC_UNUSED( i_idx
);
1781 char *psz_text
, *psz_orig
;
1782 char *psz_text2
, *psz_orig2
;
1784 if( !p_props
->jss
.b_inited
)
1786 p_props
->jss
.i_comment
= 0;
1787 p_props
->jss
.i_time_resolution
= 30;
1788 p_props
->jss
.i_time_shift
= 0;
1790 p_props
->jss
.b_inited
= true;
1793 /* Parse the main lines */
1796 const char *s
= TextGetLine( txt
);
1798 return VLC_EGENERIC
;
1800 size_t line_length
= strlen( s
);
1801 psz_orig
= malloc( line_length
+ 1 );
1804 psz_text
= psz_orig
;
1806 /* Complete time lines */
1807 int h1
, h2
, m1
, m2
, s1
, s2
, f1
, f2
;
1808 if( sscanf( s
, "%d:%d:%d.%d %d:%d:%d.%d %[^\n\r]",
1809 &h1
, &m1
, &s1
, &f1
, &h2
, &m2
, &s2
, &f2
, psz_text
) == 9 )
1811 p_subtitle
->i_start
= vlc_tick_from_sec( ( h1
*3600 + m1
* 60 + s1
) +
1812 (int64_t)( ( f1
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
) );
1813 p_subtitle
->i_stop
= vlc_tick_from_sec( ( h2
*3600 + m2
* 60 + s2
) +
1814 (int64_t)( ( f2
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
) );
1817 /* Short time lines */
1818 else if( sscanf( s
, "@%d @%d %[^\n\r]", &f1
, &f2
, psz_text
) == 3 )
1820 p_subtitle
->i_start
=
1821 vlc_tick_from_sec( (f1
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
);
1822 p_subtitle
->i_stop
=
1823 vlc_tick_from_sec( (f2
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
);
1826 /* General Directive lines */
1827 /* Only TIME and SHIFT are supported so far */
1828 else if( s
[0] == '#' )
1830 int h
= 0, m
=0, sec
= 1, f
= 1;
1834 strcpy( psz_text
, s
);
1836 switch( toupper( (unsigned char)psz_text
[1] ) )
1839 shift
= isalpha( (unsigned char)psz_text
[2] ) ? 6 : 2 ;
1840 if ( shift
> line_length
)
1843 if( sscanf( &psz_text
[shift
], "%d", &h
) )
1845 /* Negative shifting */
1852 if( sscanf( &psz_text
[shift
], "%*d:%d", &m
) )
1854 if( sscanf( &psz_text
[shift
], "%*d:%*d:%d", &sec
) )
1856 sscanf( &psz_text
[shift
], "%*d:%*d:%*d.%d", &f
);
1861 sscanf( &psz_text
[shift
], "%d:%d.%d",
1869 sscanf( &psz_text
[shift
], "%d.%d", &sec
, &f
);
1872 p_props
->jss
.i_time_shift
= ( ( h
* 3600 + m
* 60 + sec
)
1873 * p_props
->jss
.i_time_resolution
+ f
) * inv
;
1878 shift
= isalpha( (unsigned char)psz_text
[2] ) ? 8 : 2 ;
1879 if ( shift
> line_length
)
1882 sscanf( &psz_text
[shift
], "%d", &p_props
->jss
.i_time_resolution
);
1883 if( !p_props
->jss
.i_time_resolution
)
1884 p_props
->jss
.i_time_resolution
= 30;
1891 /* Unkown type line, probably a comment */
1898 while( psz_text
[ strlen( psz_text
) - 1 ] == '\\' )
1900 const char *s2
= TextGetLine( txt
);
1905 return VLC_EGENERIC
;
1908 size_t i_len
= strlen( s2
);
1912 size_t i_old
= strlen( psz_text
);
1914 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 );
1918 psz_orig
= psz_text
;
1919 strcat( psz_text
, s2
);
1922 /* Skip the blanks */
1923 while( *psz_text
== ' ' || *psz_text
== '\t' ) psz_text
++;
1925 /* Parse the directives */
1926 if( isalpha( (unsigned char)*psz_text
) || *psz_text
== '[' )
1928 while( *psz_text
&& *psz_text
!= ' ' )
1931 /* Directives are NOT parsed yet */
1932 /* This has probably a better place in a decoder ? */
1933 /* directive = malloc( strlen( psz_text ) + 1 );
1934 if( sscanf( psz_text, "%s %[^\n\r]", directive, psz_text2 ) == 2 )*/
1937 /* Skip the blanks after directives */
1938 while( *psz_text
== ' ' || *psz_text
== '\t' ) psz_text
++;
1940 /* Clean all the lines from inline comments and other stuffs */
1941 psz_orig2
= calloc( strlen( psz_text
) + 1, 1 );
1942 psz_text2
= psz_orig2
;
1944 for( ; *psz_text
!= '\0' && *psz_text
!= '\n' && *psz_text
!= '\r'; )
1949 p_props
->jss
.i_comment
++;
1952 if( p_props
->jss
.i_comment
)
1954 p_props
->jss
.i_comment
= 0;
1955 if( (*(psz_text
+ 1 ) ) == ' ' ) psz_text
++;
1959 if( !p_props
->jss
.i_comment
)
1967 if( (*(psz_text
+ 1 ) ) == ' ' || (*(psz_text
+ 1 ) ) == '\t' )
1969 if( !p_props
->jss
.i_comment
)
1976 if( (*(psz_text
+ 1 ) ) == 'n' )
1983 if( ( toupper((unsigned char)*(psz_text
+ 1 ) ) == 'C' ) ||
1984 ( toupper((unsigned char)*(psz_text
+ 1 ) ) == 'F' ) )
1989 if( (*(psz_text
+ 1 ) ) == 'B' || (*(psz_text
+ 1 ) ) == 'b' ||
1990 (*(psz_text
+ 1 ) ) == 'I' || (*(psz_text
+ 1 ) ) == 'i' ||
1991 (*(psz_text
+ 1 ) ) == 'U' || (*(psz_text
+ 1 ) ) == 'u' ||
1992 (*(psz_text
+ 1 ) ) == 'D' || (*(psz_text
+ 1 ) ) == 'N' )
1997 if( (*(psz_text
+ 1 ) ) == '~' || (*(psz_text
+ 1 ) ) == '{' ||
1998 (*(psz_text
+ 1 ) ) == '\\' )
2000 else if( ( *(psz_text
+ 1 ) == '\r' || *(psz_text
+ 1 ) == '\n' ) &&
2001 *(psz_text
+ 1 ) != '\0' )
2007 if( !p_props
->jss
.i_comment
)
2009 *psz_text2
= *psz_text
;
2016 p_subtitle
->psz_text
= psz_orig2
;
2017 msg_Dbg( p_obj
, "%s", p_subtitle
->psz_text
);
2022 static int ParsePSB( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2023 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2026 VLC_UNUSED(p_props
);
2027 VLC_UNUSED( i_idx
);
2036 const char *s
= TextGetLine( txt
);
2039 return VLC_EGENERIC
;
2041 psz_text
= malloc( strlen( s
) + 1 );
2045 if( sscanf( s
, "{%d:%d:%d}{%d:%d:%d}%[^\r\n]",
2046 &h1
, &m1
, &s1
, &h2
, &m2
, &s2
, psz_text
) == 7 )
2048 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2049 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
);
2055 /* replace | by \n */
2056 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
2058 if( psz_text
[i
] == '|' )
2061 p_subtitle
->psz_text
= psz_text
;
2065 static int64_t ParseRealTime( char *psz
, int *h
, int *m
, int *s
, int *f
)
2067 if( *psz
== '\0' ) return 0;
2068 if( sscanf( psz
, "%d:%d:%d.%d", h
, m
, s
, f
) == 4 ||
2069 sscanf( psz
, "%d:%d.%d", m
, s
, f
) == 3 ||
2070 sscanf( psz
, "%d.%d", s
, f
) == 2 ||
2071 sscanf( psz
, "%d:%d", m
, s
) == 2 ||
2072 sscanf( psz
, "%d", s
) == 1 )
2074 return vlc_tick_from_sec((( *h
* 60 + *m
) * 60 ) + *s
)
2075 + VLC_TICK_FROM_MS(*f
* 10);
2077 else return VLC_EGENERIC
;
2080 static int ParseRealText( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2081 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2084 VLC_UNUSED(p_props
);
2085 VLC_UNUSED( i_idx
);
2086 char *psz_text
= NULL
;
2090 int h1
= 0, m1
= 0, s1
= 0, f1
= 0;
2091 int h2
= 0, m2
= 0, s2
= 0, f2
= 0;
2092 const char *s
= TextGetLine( txt
);
2096 return VLC_EGENERIC
;
2098 psz_text
= malloc( strlen( s
) + 1 );
2102 /* Find the good begining. This removes extra spaces at the beginning
2104 char *psz_temp
= strcasestr( s
, "<time");
2105 if( psz_temp
!= NULL
)
2107 char psz_end
[12], psz_begin
[12];
2108 /* Line has begin and end */
2109 if( ( sscanf( psz_temp
,
2110 "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\" %*[e|E]nd=\"%11[^\"]%*[^>]%[^\n\r]",
2111 psz_begin
, psz_end
, psz_text
) != 3 ) &&
2112 /* Line has begin and no end */
2114 "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\"%*[^>]%[^\n\r]",
2115 psz_begin
, psz_text
) != 2) )
2116 /* Line is not recognized */
2122 int64_t i_time
= ParseRealTime( psz_begin
, &h1
, &m1
, &s1
, &f1
);
2123 p_subtitle
->i_start
= i_time
>= 0 ? i_time
: 0;
2125 i_time
= ParseRealTime( psz_end
, &h2
, &m2
, &s2
, &f2
);
2126 p_subtitle
->i_stop
= i_time
>= 0 ? i_time
: -1;
2131 /* Get the following Lines */
2134 const char *s
= TextGetLine( txt
);
2139 return VLC_EGENERIC
;
2142 size_t i_len
= strlen( s
);
2143 if( i_len
== 0 ) break;
2145 if( strcasestr( s
, "<time" ) ||
2146 strcasestr( s
, "<clear/") )
2148 TextPreviousLine( txt
);
2152 size_t i_old
= strlen( psz_text
);
2154 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
2158 strcat( psz_text
, s
);
2159 strcat( psz_text
, "\n" );
2162 /* Remove the starting ">" that remained after the sscanf */
2163 memmove( &psz_text
[0], &psz_text
[1], strlen( psz_text
) );
2165 p_subtitle
->psz_text
= psz_text
;
2170 static int ParseDKS( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2171 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2174 VLC_UNUSED(p_props
);
2175 VLC_UNUSED( i_idx
);
2183 char *s
= TextGetLine( txt
);
2186 return VLC_EGENERIC
;
2188 psz_text
= malloc( strlen( s
) + 1 );
2192 if( sscanf( s
, "[%d:%d:%d]%[^\r\n]",
2193 &h1
, &m1
, &s1
, psz_text
) == 4 )
2195 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2197 s
= TextGetLine( txt
);
2201 return VLC_EGENERIC
;
2204 if( sscanf( s
, "[%d:%d:%d]", &h2
, &m2
, &s2
) == 3 )
2205 p_subtitle
->i_stop
= vlc_tick_from_sec(h2
* 3600 + m2
* 60 + s2
);
2207 p_subtitle
->i_stop
= -1;
2213 /* replace [br] by \n */
2215 while( ( p
= strstr( psz_text
, "[br]" ) ) )
2218 memmove( p
, &p
[3], strlen(&p
[3])+1 );
2221 p_subtitle
->psz_text
= psz_text
;
2225 static int ParseSubViewer1( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2226 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2229 VLC_UNUSED(p_props
);
2230 VLC_UNUSED( i_idx
);
2237 char *s
= TextGetLine( txt
);
2240 return VLC_EGENERIC
;
2242 if( sscanf( s
, "[%d:%d:%d]", &h1
, &m1
, &s1
) == 3 )
2244 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2246 s
= TextGetLine( txt
);
2248 return VLC_EGENERIC
;
2250 psz_text
= strdup( s
);
2254 s
= TextGetLine( txt
);
2258 return VLC_EGENERIC
;
2261 if( sscanf( s
, "[%d:%d:%d]", &h2
, &m2
, &s2
) == 3 )
2262 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
);
2264 p_subtitle
->i_stop
= -1;
2270 p_subtitle
->psz_text
= psz_text
;
2275 static int ParseCommonSBV( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2276 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2279 VLC_UNUSED( i_idx
);
2280 VLC_UNUSED( p_props
);
2285 const char *s
= TextGetLine( txt
);
2286 int h1
= 0, m1
= 0, s1
= 0, d1
= 0;
2287 int h2
= 0, m2
= 0, s2
= 0, d2
= 0;
2290 return VLC_EGENERIC
;
2292 if( sscanf( s
,"%d:%d:%d.%d,%d:%d:%d.%d",
2294 &h2
, &m2
, &s2
, &d2
) == 8 )
2296 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
2297 VLC_TICK_FROM_MS( d1
);
2299 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
2300 VLC_TICK_FROM_MS( d2
);
2301 if( p_subtitle
->i_start
< p_subtitle
->i_stop
)
2306 /* Now read text until an empty line */
2307 psz_text
= strdup("");
2313 const char *s
= TextGetLine( txt
);
2317 i_len
= s
? strlen( s
) : 0;
2320 p_subtitle
->psz_text
= psz_text
;
2324 i_old
= strlen( psz_text
);
2325 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
2329 strcat( psz_text
, s
);
2330 strcat( psz_text
, "\n" );
2334 static int ParseSCC( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2335 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2338 VLC_UNUSED( i_idx
);
2339 VLC_UNUSED( p_props
);
2341 static const struct rates
2344 vlc_rational_t rate
;
2345 bool b_drop_allowed
;
2347 { 2398, { 24000, 1001 }, false },
2348 { 2400, { 24, 1 }, false },
2349 { 2500, { 25, 1 }, false },
2350 { 2997, { 30000, 1001 }, true }, /* encoding rate */
2351 { 3000, { 30, 1 }, false },
2352 { 5000, { 50, 1 }, false },
2353 { 5994, { 60000, 1001 }, true },
2354 { 6000, { 60, 1 }, false },
2356 const struct rates
*p_rate
= &framerates
[3];
2357 float f_fps
= var_GetFloat( p_obj
, "sub-original-fps" );
2360 for( size_t i
=0; i
<ARRAY_SIZE(framerates
); i
++ )
2362 if( (unsigned)(f_fps
* 100) == framerates
[i
].val
)
2364 p_rate
= &framerates
[i
];
2372 const char *psz_line
= TextGetLine( txt
);
2374 return VLC_EGENERIC
;
2376 unsigned h
, m
, s
, f
;
2378 if( sscanf( psz_line
, "%u:%u:%u%c%u ", &h
, &m
, &s
, &c
, &f
) != 5 ||
2379 ( c
!= ':' && c
!= ';' ) )
2382 /* convert everything to seconds */
2383 uint64_t i_frames
= h
* 3600 + m
* 60 + s
;
2385 if( c
== ';' && p_rate
->b_drop_allowed
) /* dropframe */
2387 /* convert to frame # to be accurate between inter drop drift
2388 * of 18 frames see http://andrewduncan.net/timecodes/ */
2389 const unsigned i_mins
= h
* 60 + m
;
2390 i_frames
= i_frames
* p_rate
[+1].rate
.num
+ f
2391 - (p_rate
[+1].rate
.den
* 2 * (i_mins
- i_mins
% 10));
2395 /* convert to frame # at 29.97 */
2396 i_frames
= i_frames
* framerates
[3].rate
.num
/ framerates
[3].rate
.den
+ f
;
2398 p_subtitle
->i_start
= VLC_TICK_0
+ vlc_tick_from_sec(i_frames
)*
2399 p_rate
->rate
.den
/ p_rate
->rate
.num
;
2400 p_subtitle
->i_stop
= -1;
2402 const char *psz_text
= strchr( psz_line
, '\t' );
2403 if( !psz_text
&& !(psz_text
= strchr( psz_line
, ' ' )) )
2406 if ( psz_text
[1] == '\0' )
2409 p_subtitle
->psz_text
= strdup( psz_text
+ 1 );
2410 if( !p_subtitle
->psz_text
)
2419 /* Matches filename.xx.srt */
2420 static char * get_language_from_filename( const char * psz_sub_file
)
2422 char *psz_ret
= NULL
;
2423 char *psz_tmp
, *psz_language_begin
;
2425 if( !psz_sub_file
) return NULL
;
2426 char *psz_work
= strdup( psz_sub_file
);
2428 /* Removing extension, but leaving the dot */
2429 psz_tmp
= strrchr( psz_work
, '.' );
2433 psz_language_begin
= strrchr( psz_work
, '.' );
2434 if( psz_language_begin
)
2435 psz_ret
= strdup(++psz_language_begin
);