]> git.sesse.net Git - vlc/blob - modules/demux/util/sub.c
ac1fccb56e85309f2b516f3f2e4a9aef40be0ed5
[vlc] / modules / demux / util / sub.c
1 /*****************************************************************************
2  * sub.c
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: sub.c,v 1.2 2003/01/21 16:46:17 fenrir Exp $
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  * 
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/types.h>
31
32 #include <vlc/vlc.h>
33 #include <vlc/input.h>
34
35 #include "video.h"
36
37 #include "sub.h"
38
39
40 static int  Open ( vlc_object_t *p_this );
41
42 static int  sub_open ( subtitle_demux_t *p_sub, 
43                        input_thread_t  *p_input,
44                        char  *psz_name,
45                        mtime_t i_microsecperframe );
46 static int  sub_demux( subtitle_demux_t *p_sub, mtime_t i_maxdate );
47 static int  sub_seek ( subtitle_demux_t *p_sub, mtime_t i_date );
48 static void sub_close( subtitle_demux_t *p_sub );
49
50 static int  sub_MicroDvdRead( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe );
51 static int  sub_SubRipRead( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe );
52 static int  sub_SSA1Read( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe );
53 static int  sub_SSA2_4Read( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe );
54
55 static void sub_fix( subtitle_demux_t *p_sub );
56
57 static char *ppsz_sub_type[] = { "microdvd", "subrip", "ssa1", "ssa2-4", NULL };
58 /*****************************************************************************
59  * Module descriptor
60  *****************************************************************************/
61
62 #define SUB_FPS_LONGTEXT \
63     "Override frames per second" \
64     "It will work only with MicroDVD"
65 #define SUB_TYPE_LONGTEXT \
66     "One from \"microdvd\", \"subrip\", \"ssa1\", \"ssa2-4\"" \
67     "(nothing for autodetection, It should always work)"
68
69 vlc_module_begin();
70     set_description( _("text subtitle demux") );
71     set_capability( "subtitle demux", 12 );
72     add_category_hint( "subtitle", NULL );
73         add_string( "sub-file", NULL, NULL,
74                     "subtitle file name", "subtitle file name" );
75         add_float( "sub-fps", 0.0, NULL, 
76                    "override frames per second",
77                    SUB_FPS_LONGTEXT );
78         add_integer( "sub-delay", 0, NULL,
79                      "delay subtitles (in 1/10s)", 
80                      "delay subtitles (in 1/10s)" );
81         add_string_from_list( "sub-type", NULL, ppsz_sub_type, NULL,
82                               "subtitle type", 
83                               SUB_TYPE_LONGTEXT );
84     set_callbacks( Open, NULL );
85 vlc_module_end();
86
87 /*****************************************************************************
88  * Module initializer
89  *****************************************************************************/
90 static int Open ( vlc_object_t *p_this )
91 {
92     subtitle_demux_t *p_sub = (subtitle_demux_t*)p_this;
93
94     p_sub->pf_open  = sub_open;
95     p_sub->pf_demux = sub_demux;
96     p_sub->pf_seek  = sub_seek;
97     p_sub->pf_close = sub_close;
98     
99     return VLC_SUCCESS;
100 }
101 #define MAX_TRY     256
102 #define MAX_LINE    2048
103 /*****************************************************************************
104  * sub_open: Open a subtitle file and add subtitle ES
105  *****************************************************************************/
106 static int  sub_open ( subtitle_demux_t *p_sub, 
107                        input_thread_t  *p_input,
108                        char     *psz_name,
109                        mtime_t i_microsecperframe )
110 {
111     FILE *p_file;
112     char    *psz_file_type;
113     char    buffer[MAX_LINE + 1];
114     int     i_try;
115     int     i_sub_type;
116     int     i_max;
117     int (*pf_read_subtitle)( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe ) = NULL;
118    
119         
120     p_sub->i_sub_type = SUB_TYPE_UNKNOWN;
121     p_sub->p_es = NULL;
122     p_sub->i_subtitles = 0;
123     p_sub->subtitle = NULL;
124     p_sub->p_input = p_input;
125
126     if( !psz_name || !*psz_name)
127     {
128         psz_name = config_GetPsz( p_sub, "sub-file" );
129         if( !psz_name || !*psz_name )
130         {
131             return( -1 );
132         }
133     }
134
135     if(  config_GetFloat( p_sub, "sub-fps" ) >= 1.0 )
136     {
137         i_microsecperframe = (mtime_t)( (float)1000000 / 
138                                         config_GetFloat( p_sub, "sub-fps" ) );
139     }
140     else if( i_microsecperframe <= 0 )
141     {
142         i_microsecperframe = 40000; /* default: 25fps */
143     }
144
145     /* *** Open the file *** */
146     if( !( p_file = fopen( psz_name, "r" ) ) )
147     {
148         msg_Err( p_sub, "cannot open `%s' subtitle file", psz_name );
149
150     }
151     else
152     {
153         msg_Dbg( p_sub, "opened `%s'", psz_name );
154     }
155
156     psz_file_type = config_GetPsz( p_sub, "sub-type" );
157     if( psz_file_type && *psz_file_type)
158     {
159         if( !strcmp( psz_file_type, "microdvd" ) )
160         {
161             i_sub_type = SUB_TYPE_MICRODVD;
162         }
163         else if( !strcmp( psz_file_type, "subrip" ) )
164         {
165             i_sub_type = SUB_TYPE_SUBRIP;
166         }
167         else if( !strcmp( psz_file_type, "ssa1" ) )
168         {
169             i_sub_type = SUB_TYPE_SSA1;
170         }
171         else if( !strcmp( psz_file_type, "ssa2-4" ) )
172         {
173             i_sub_type = SUB_TYPE_SSA2_4;
174         }
175         else
176         {
177             i_sub_type = SUB_TYPE_UNKNOWN;
178         }
179     }
180     else
181     {
182         i_sub_type = SUB_TYPE_UNKNOWN;
183     }
184    
185     /* *** Now try to autodetect subtitle format *** */
186     if( i_sub_type == SUB_TYPE_UNKNOWN )
187     {
188         msg_Dbg( p_input, "trying to autodetect file format" );
189         for( i_try = 0; i_try < MAX_TRY; i_try++ )
190         {
191             int i_dummy;
192             if( fgets( buffer, MAX_LINE, p_file ) <= 0 )
193             {
194                 break;
195             }
196
197             if( sscanf( buffer, "{%d}{%d}", &i_dummy, &i_dummy ) == 2 ||
198                 sscanf( buffer, "{%d}{}", &i_dummy ) == 1)
199             {
200                 i_sub_type = SUB_TYPE_MICRODVD;
201                 break;
202             }
203             else if( sscanf( buffer, 
204                              "%d:%d:%d,%d --> %d:%d:%d,%d",
205                              &i_dummy,&i_dummy,&i_dummy,&i_dummy, 
206                              &i_dummy,&i_dummy,&i_dummy,&i_dummy ) == 8 )
207             {
208                 i_sub_type = SUB_TYPE_SUBRIP;
209                 break;
210             }
211             else if( sscanf( buffer, 
212                              "!: This is a Sub Station Alpha v%d.x script.",
213                              &i_dummy ) == 1)
214             {
215                 if( i_dummy <= 1 )
216                 {
217                     i_sub_type = SUB_TYPE_SSA1;
218                 }
219                 else
220                 {
221                     i_sub_type = SUB_TYPE_SSA2_4; // I hop this will work
222                 }
223             }
224             else if( !strcmp( buffer,
225                               "Dialogue: Marked" ) )
226             {
227                 i_sub_type = SUB_TYPE_SSA2_4; // could be wrong
228             }
229                               
230             
231         }
232     }
233
234     /* *** Load this file in memory *** */
235     switch( i_sub_type )
236     {
237         case SUB_TYPE_MICRODVD:
238             msg_Dbg( p_input, "detected MicroDVD format" );
239             pf_read_subtitle = sub_MicroDvdRead;
240             break;
241         case SUB_TYPE_SUBRIP:
242             msg_Dbg( p_input, "detected SubRIP format" );
243             pf_read_subtitle = sub_SubRipRead;
244             break;
245         case SUB_TYPE_SSA1:
246             msg_Dbg( p_input, "detected SSAv1 Script format" );
247             pf_read_subtitle = sub_SSA1Read;
248             break;
249         case SUB_TYPE_SSA2_4:
250             msg_Dbg( p_input, "detected SSAv2-4 Script format" );
251             pf_read_subtitle = sub_SSA2_4Read;
252             break;
253         default:
254             msg_Err( p_sub, "unknown subtitile file" );
255             fclose( p_file );
256             return( -1 );
257     }
258     
259     if( fseek( p_file, 0L, SEEK_SET ) < 0 )
260     {
261         msg_Err( p_input, "cannot read file from begining" );
262         fclose( p_file );
263         return( -1 );
264     }
265     for( i_max = 0;; )
266     {   
267         if( p_sub->i_subtitles <= i_max )
268         {
269             i_max += 128;
270             if( p_sub->subtitle )
271             {
272                 p_sub->subtitle = realloc( p_sub->subtitle, 
273                                            sizeof( subtitle_t ) * i_max );
274             }
275             else
276             {
277                 p_sub->subtitle = malloc( sizeof( subtitle_t ) * i_max );
278             }
279         }
280         if( pf_read_subtitle( p_file, 
281                               p_sub->subtitle + p_sub->i_subtitles, 
282                               i_microsecperframe ) < 0 )
283         {
284             break;
285         }
286         p_sub->i_subtitles++;
287     }
288     msg_Dbg( p_sub, "loaded %d subtitles", p_sub->i_subtitles );
289
290     
291     /* *** Close the file *** */
292     fclose( p_file );
293
294     /* *** fix subtitle (order and time) *** */
295     p_sub->i_subtitle = 0;  // will be modified by sub_fix
296     sub_fix( p_sub );
297     
298     /* *** add subtitle ES *** */
299     vlc_mutex_lock( &p_input->stream.stream_lock );
300     p_sub->p_es = input_AddES( p_input,
301                                p_input->stream.p_selected_program,
302                                0xff,    // FIXME
303                                0 );
304     vlc_mutex_unlock( &p_input->stream.stream_lock );
305
306     p_sub->p_es->i_stream_id = 0xff;    // FIXME
307     p_sub->p_es->i_fourcc    = VLC_FOURCC( 's','u','b','t' );
308     p_sub->p_es->i_cat       = SPU_ES;
309
310     p_sub->i_previously_selected = 0;
311     return( 0 );
312 }
313
314 /*****************************************************************************
315  * sub_demux: Send subtitle to decoder until i_maxdate
316  *****************************************************************************/
317 static int  sub_demux( subtitle_demux_t *p_sub, mtime_t i_maxdate )
318 {
319
320     if( p_sub->p_es->p_decoder_fifo && !p_sub->i_previously_selected )
321     {
322         p_sub->i_previously_selected = 1;
323         p_sub->pf_seek( p_sub, i_maxdate );
324         return( 0 );
325     }
326     else if( !p_sub->p_es->p_decoder_fifo && p_sub->i_previously_selected )
327     {
328         p_sub->i_previously_selected = 0;
329         return( 0 );
330     }
331
332     while( p_sub->i_subtitle < p_sub->i_subtitles &&
333            p_sub->subtitle[p_sub->i_subtitle].i_start < i_maxdate )
334     {
335         pes_packet_t    *p_pes;
336         data_packet_t   *p_data;
337         
338         int i_len;
339         
340         i_len = strlen( p_sub->subtitle[p_sub->i_subtitle].psz_text ) + 1;
341         
342         if( i_len <= 1 )
343         {
344             /* empty subtitle */
345             p_sub->i_subtitle++;
346             continue;
347         }
348         if( !( p_pes = input_NewPES( p_sub->p_input->p_method_data ) ) )
349         {
350             p_sub->i_subtitle++;
351             continue;
352         }
353         
354         if( !( p_data = input_NewPacket( p_sub->p_input->p_method_data, 
355                                          i_len ) ) )
356         {
357             input_DeletePES( p_sub->p_input->p_method_data, p_pes );
358             p_sub->i_subtitle++;
359             continue;
360         }
361         
362         p_pes->i_pts = 
363             input_ClockGetTS( p_sub->p_input, 
364                               p_sub->p_input->stream.p_selected_program,
365                               p_sub->subtitle[p_sub->i_subtitle].i_start*9/100);
366         if( p_sub->subtitle[p_sub->i_subtitle].i_stop > 0 )
367         {
368             /* FIXME kludge ... 
369              * i_dts means end of display...
370              */
371             p_pes->i_dts = 
372                 input_ClockGetTS( p_sub->p_input, 
373                               p_sub->p_input->stream.p_selected_program,
374                               p_sub->subtitle[p_sub->i_subtitle].i_stop *9/100);
375         }
376         else
377         {
378             p_pes->i_dts = 0;
379         }
380         p_pes->i_nb_data = 1;
381         p_pes->p_first = 
382             p_pes->p_last = p_data;
383         p_pes->i_pes_size = i_len;
384
385         memcpy( p_data->p_payload_start, 
386                 p_sub->subtitle[p_sub->i_subtitle].psz_text, 
387                 i_len );
388         if( p_sub->p_es->p_decoder_fifo )
389         {
390
391             input_DecodePES( p_sub->p_es->p_decoder_fifo, p_pes );
392         }
393         else
394         {
395             input_DeletePES( p_sub->p_input->p_method_data, p_pes );
396         }
397         
398         
399         p_sub->i_subtitle++;
400     }
401     return( 0 );
402 }
403
404 /*****************************************************************************
405  * sub_seek: Seek to i_date
406  *****************************************************************************/
407 static int  sub_seek ( subtitle_demux_t *p_sub, mtime_t i_date )
408 {
409     /* should be fast enough... */
410     p_sub->i_subtitle = 0;
411     while( p_sub->i_subtitle < p_sub->i_subtitles &&
412            p_sub->subtitle[p_sub->i_subtitle].i_start < i_date )
413     {
414         p_sub->i_subtitle++;
415     }
416
417     return( 0 );
418 }
419
420 /*****************************************************************************
421  * sub_close: Close subtitle demux
422  *****************************************************************************/
423 static void sub_close( subtitle_demux_t *p_sub )
424 {
425     if( p_sub->subtitle )
426     {
427         int i;
428         for( i = 0; i < p_sub->i_subtitles; i++ )
429         {
430             if( p_sub->subtitle[i].psz_text )
431             {
432                 free( p_sub->subtitle[i].psz_text );
433             }
434         }
435         free( p_sub->subtitle );
436     }
437 }
438 /*****************************************************************************
439  *
440  * sub_fix: fix time stamp and order of subtitle
441  *****************************************************************************/
442 static void  sub_fix( subtitle_demux_t *p_sub )
443 {
444     int     i;
445     mtime_t i_delay;
446     int     i_index;
447     int     i_done;
448
449     /* *** fix order (to be sure...) *** */
450     /* We suppose that there are near in order and this durty bubble sort
451      * wont take too much time
452      */
453     do
454     {
455         i_done = 1;
456         for( i_index = 1; i_index < p_sub->i_subtitles; i_index++ )
457         {
458             if( p_sub->subtitle[i_index].i_start <
459                     p_sub->subtitle[i_index - 1].i_start )
460             {
461                 subtitle_t sub_xch;
462                 memcpy( &sub_xch, 
463                         p_sub->subtitle + i_index - 1, 
464                         sizeof( subtitle_t ) );
465                 memcpy( p_sub->subtitle + i_index - 1, 
466                         p_sub->subtitle + i_index, 
467                         sizeof( subtitle_t ) );
468                 memcpy( p_sub->subtitle + i_index,
469                         &sub_xch,
470                         sizeof( subtitle_t ) );
471                 i_done = 0;
472             }
473         }
474     } while( !i_done );
475         
476     
477     /* *** and at the end add delay *** */
478     i_delay = (mtime_t)config_GetInt( p_sub, "sub-delay" ) * 100000;
479     if( i_delay != 0 )
480     {
481         for( i = 0; i < p_sub->i_subtitles; i++ )
482         {
483             p_sub->subtitle[i].i_start += i_delay;
484             p_sub->subtitle[i].i_stop += i_delay;
485             if( p_sub->subtitle[i].i_start < 0 )
486             {
487                 p_sub->i_subtitle = i + 1;
488             }
489         }
490     }
491 }
492
493
494
495 /*****************************************************************************
496  * Specific Subtitle function 
497  *****************************************************************************/
498 static int  sub_MicroDvdRead( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe)
499 {
500     /*
501      * each line:
502      *  {n1}{n2}Line1|Line2|Line3....
503      * where n1 and n2 are the video frame number...
504      *
505      */
506     char buffer[MAX_LINE + 1];
507     char buffer_text[MAX_LINE + 1];
508     uint32_t    i_start;
509     uint32_t    i_stop;
510     int         i;
511     
512     for( ;; )
513     {
514         if( fgets( buffer, MAX_LINE, p_file ) <= 0) 
515         {
516             return( -1 );
517         }
518         i_start = 0;
519         i_stop  = 0;
520         if( sscanf( buffer, "{%d}{}%[^\r\n]", &i_start, buffer_text ) == 2 ||
521             sscanf( buffer, "{%d}{%d}%[^\r\n]", &i_start, &i_stop, buffer_text ) == 3)
522         {
523             break;
524         }
525     }
526     /* replace | by \n */
527     for( i = 0; i < MAX_LINE; i++ )
528     {
529         if( buffer_text[i] == '|' )
530         {
531             buffer_text[i] = '\n';
532         }
533     }
534     p_subtitle->i_start = (mtime_t)i_start * (mtime_t)i_microsecperframe;
535     p_subtitle->i_stop  = (mtime_t)i_stop  * (mtime_t)i_microsecperframe;
536     p_subtitle->psz_text = strndup( buffer_text, MAX_LINE );
537     return( 0 );
538 }
539
540 static int  sub_SubRipRead( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe )
541 {
542     /*
543      * n
544      * h1:m1:s1,d1 --> h2:m2:s2,d2
545      * Line1
546      * Line2
547      * ...
548      * [empty line]
549      * 
550      */
551     char buffer[MAX_LINE + 1];
552     char buffer_text[ 10 * MAX_LINE];
553     int  i_buffer_text;
554     mtime_t     i_start;
555     mtime_t     i_stop;
556
557     for( ;; )
558     {
559         int h1, m1, s1, d1, h2, m2, s2, d2;
560         if( fgets( buffer, MAX_LINE, p_file ) <= 0)
561         {
562             return( -1 );
563         }
564         if( sscanf( buffer,
565                     "%d:%d:%d,%d --> %d:%d:%d,%d",
566                     &h1, &m1, &s1, &d1,
567                     &h2, &m2, &s2, &d2 ) == 8 )
568         {
569             i_start = ( (mtime_t)h1 * 3600*1000 + 
570                         (mtime_t)m1 * 60*1000 + 
571                         (mtime_t)s1 * 1000 + 
572                         (mtime_t)d1 ) * 1000;
573
574             i_stop  = ( (mtime_t)h2 * 3600*1000 + 
575                         (mtime_t)m2 * 60*1000 + 
576                         (mtime_t)s2 * 1000 + 
577                         (mtime_t)d2 ) * 1000;
578
579             /* Now read text until an empty line */
580             for( i_buffer_text = 0;; )
581             {
582                 int i_len;
583                 if( fgets( buffer, MAX_LINE, p_file ) <= 0) 
584                 {
585                     return( -1 );
586                 }
587                 buffer[MAX_LINE] = '\0'; // just in case
588                 i_len = strlen( buffer );
589                 if( buffer[0] == '\r' || buffer[0] == '\n' || i_len <= 1 )
590                 {
591                     // empty line -> end of this subtitle
592                     buffer_text[__MAX( i_buffer_text - 1, 0 )] = '\0';
593                     p_subtitle->i_start = i_start;
594                     p_subtitle->i_stop = i_stop;
595                     p_subtitle->psz_text = strdup( buffer_text );
596                     return( 0 );
597                 }
598                 else
599                 {
600                     if( i_buffer_text + i_len + 1 < 10 * MAX_LINE )
601                     {
602                         memcpy( buffer_text + i_buffer_text,
603                                 buffer,
604                                 i_len );
605                         i_buffer_text += i_len;
606
607                         buffer_text[i_buffer_text] = '\n';
608                         i_buffer_text++;
609                     }
610                 }
611             }
612         }
613     }
614    
615 }
616
617
618 static int  sub_SSARead( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe, int i_comma_count )
619 {
620     char buffer[MAX_LINE + 1];
621     char buffer_text[ 10 * MAX_LINE];
622     char *p_buffer_text;
623     mtime_t     i_start;
624     mtime_t     i_stop;
625     int         i_comma;
626     int         i_text;
627     
628     for( ;; )
629     {
630         int h1, m1, s1, c1, h2, m2, s2, c2;
631         int i_dummy;
632         if( fgets( buffer, MAX_LINE, p_file ) <= 0) 
633         {
634             return( -1 );
635         }
636         if( sscanf( buffer, 
637                     "Dialogue: Marked=%d,%d:%d:%d.%d,%d:%d:%d.%d,%[^\r\n]",
638                     &i_dummy, 
639                     &h1, &m1, &s1, &c1,
640                     &h2, &m2, &s2, &c2,
641                     buffer_text ) == 10 )
642         {
643             i_start = ( (mtime_t)h1 * 3600*1000 + 
644                         (mtime_t)m1 * 60*1000 + 
645                         (mtime_t)s1 * 1000 + 
646                         (mtime_t)c1 * 10 ) * 1000;
647
648             i_stop  = ( (mtime_t)h2 * 3600*1000 + 
649                         (mtime_t)m2 * 60*1000 + 
650                         (mtime_t)s2 * 1000 + 
651                         (mtime_t)c2 * 10 ) * 1000;
652             
653             p_buffer_text = buffer_text;
654             i_comma = 3;
655             while( i_comma < i_comma_count && 
656                    *p_buffer_text != '\0' )
657             {   
658                 if( *p_buffer_text == ',' )
659                 {
660                     i_comma++;
661                 }
662                 p_buffer_text++;
663             }
664             p_subtitle->psz_text = malloc( strlen( p_buffer_text ) + 1);
665             i_text = 0;
666             while( *p_buffer_text )
667             {
668                 if( *p_buffer_text == '\\' && ( *p_buffer_text =='n' || *p_buffer_text =='N' ) )
669                 {
670                     p_subtitle->psz_text[i_text] = '\n';
671                     i_text++;
672                     p_buffer_text += 2;
673                 }
674                 else if( *p_buffer_text == '{' && *p_buffer_text == '\\')
675                 {
676                     while( *p_buffer_text && *p_buffer_text != '}' )
677                     {
678                         p_buffer_text++;
679                     }
680                 }
681                 else
682                 {
683                     p_subtitle->psz_text[i_text] = *p_buffer_text;
684                     i_text++;
685                     p_buffer_text++;
686                 }
687             }
688             p_subtitle->psz_text[i_text] = '\0';
689             p_subtitle->i_start = i_start;
690             p_subtitle->i_stop = i_stop;
691             return( 0 );
692         }
693     }
694 }
695
696 static int  sub_SSA1Read( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe )
697 {
698     return( sub_SSARead( p_file, p_subtitle, i_microsecperframe, 8 ) );
699 }
700 static int  sub_SSA2_4Read( FILE *p_file, subtitle_t *p_subtitle, mtime_t i_microsecperframe )
701 {
702     return( sub_SSARead( p_file, p_subtitle, i_microsecperframe, 9 ) );
703 }
704