subtitle.c 68.3 KB
Newer Older
1
/*****************************************************************************
hartman's avatar
hartman committed
2
 * subtitle.c: Demux for subtitle text files.
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 1999-2007 VLC authors and VideoLAN
5
 * $Id$
6
7
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
hartman's avatar
hartman committed
8
 *          Derk-Jan Hartman <hartman at videolan dot org>
9
 *          Jean-Baptiste Kempf <jb@videolan.org>
10
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
11
12
13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
14
 * (at your option) any later version.
Laurent Aimar's avatar
Laurent Aimar committed
15
 *
16
17
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
18
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
21
22
23
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
26
27
28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29

30
31
32
33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include <vlc_common.h>
35
#include <vlc_plugin.h>
zorglub's avatar
zorglub committed
36
#include <vlc_input.h>
37
#include <vlc_memory.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
38

39
#include <ctype.h>
40
#include <math.h>
41
#include <assert.h>
42

zorglub's avatar
zorglub committed
43
44
#include <vlc_demux.h>
#include <vlc_charset.h>
45

46
47
#include "subtitle_helper.h"

48
49
50
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Laurent Aimar's avatar
   
Laurent Aimar committed
51
52
53
static int  Open ( vlc_object_t *p_this );
static void Close( vlc_object_t *p_this );

gbazin's avatar
   
gbazin committed
54
#define SUB_DELAY_LONGTEXT \
55
    N_("Apply a delay to all subtitles (in 1/10s, eg 100 means 10s).")
56
#define SUB_FPS_LONGTEXT \
57
58
    N_("Override the normal frames per second settings. " \
    "This will only work with MicroDVD and SubRIP (SRT) subtitles.")
59
#define SUB_TYPE_LONGTEXT \
60
    N_("Force the subtiles format. Selecting \"auto\" means autodetection and should always work.")
61
62
#define SUB_DESCRIPTION_LONGTEXT \
    N_("Override the default track description.")
63

64
static const char *const ppsz_sub_type[] =
Laurent Aimar's avatar
   
Laurent Aimar committed
65
66
{
    "auto", "microdvd", "subrip", "subviewer", "ssa1",
67
    "ssa2-4", "ass", "vplayer", "sami", "dvdsubtitle", "mpl2",
68
    "aqt", "pjs", "mpsub", "jacosub", "psb", "realtext", "dks",
69
    "subviewer1", "vtt", "sbv"
Laurent Aimar's avatar
   
Laurent Aimar committed
70
};
71

72
73
vlc_module_begin ()
    set_shortname( N_("Subtitles"))
74
    set_description( N_("Text subtitle parser") )
75
76
77
    set_capability( "demux", 0 )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_DEMUX )
78
    add_float( "sub-fps", 0.0,
79
               N_("Frames per Second"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
80
               SUB_FPS_LONGTEXT, true )
81
    add_integer( "sub-delay", 0,
82
               N_("Subtitle delay"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
83
               SUB_DELAY_LONGTEXT, true )
84
    add_string( "sub-type", "auto", N_("Subtitle format"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
85
                SUB_TYPE_LONGTEXT, true )
86
        change_string_list( ppsz_sub_type, ppsz_sub_type )
87
    add_string( "sub-description", NULL, N_("Subtitle description"),
88
                SUB_DESCRIPTION_LONGTEXT, true )
89
    set_callbacks( Open, Close )
Laurent Aimar's avatar
   
Laurent Aimar committed
90

91
92
    add_shortcut( "subtitle" )
vlc_module_end ()
93
94

/*****************************************************************************
Laurent Aimar's avatar
   
Laurent Aimar committed
95
 * Prototypes:
96
 *****************************************************************************/
97
enum subtitle_type_e
98
{
Laurent Aimar's avatar
   
Laurent Aimar committed
99
100
101
102
103
    SUB_TYPE_UNKNOWN = -1,
    SUB_TYPE_MICRODVD,
    SUB_TYPE_SUBRIP,
    SUB_TYPE_SSA1,
    SUB_TYPE_SSA2_4,
104
    SUB_TYPE_ASS,
Laurent Aimar's avatar
   
Laurent Aimar committed
105
106
    SUB_TYPE_VPLAYER,
    SUB_TYPE_SAMI,
107
108
    SUB_TYPE_SUBVIEWER, /* SUBVIEWER 2 */
    SUB_TYPE_DVDSUBTITLE, /* Mplayer calls it subviewer2 */
109
    SUB_TYPE_MPL2,
110
    SUB_TYPE_AQT,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
111
    SUB_TYPE_PJS,
112
    SUB_TYPE_MPSUB,
113
    SUB_TYPE_JACOSUB,
114
    SUB_TYPE_PSB,
115
    SUB_TYPE_RT,
116
    SUB_TYPE_DKS,
Arun Pandian G's avatar
Arun Pandian G committed
117
    SUB_TYPE_SUBVIEW1, /* SUBVIEWER 1 - mplayer calls it subrip09,
118
                         and Gnome subtitles SubViewer 1.0 */
119
120
    SUB_TYPE_VTT,
    SUB_TYPE_SBV
Laurent Aimar's avatar
   
Laurent Aimar committed
121
};
122
123
124
125
126
127
128

typedef struct
{
    int     i_line_count;
    int     i_line;
    char    **line;
} text_t;
129

Laurent Aimar's avatar
   
Laurent Aimar committed
130
131
static int  TextLoad( text_t *, stream_t *s );
static void TextUnload( text_t * );
132

Laurent Aimar's avatar
   
Laurent Aimar committed
133
typedef struct
134
{
135
136
    int64_t i_start;
    int64_t i_stop;
137

Laurent Aimar's avatar
   
Laurent Aimar committed
138
139
    char    *psz_text;
} subtitle_t;
140

141
typedef struct
142
{
143
    enum subtitle_type_e i_type;
Laurent Aimar's avatar
   
Laurent Aimar committed
144
    int64_t     i_microsecperframe;
145

146
    char        *psz_header; /* SSA */
147
148
149
150
151
152
153
154
155

    struct
    {
        bool b_inited;

        int i_comment;
        int i_time_resolution;
        int i_time_shift;
    } jss;
156

157
158
159
160
161
162
163
    struct
    {
        bool  b_inited;

        float f_total;
        float f_factor;
    } mpsub;
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

} subs_properties_t;

struct demux_sys_t
{
    text_t      txt;
    es_out_id_t *es;

    int64_t     i_next_demux_date;

    struct
    {
        subtitle_t *p_array;
        size_t      i_count;
        size_t      i_current;
    } subtitles;

    int64_t     i_length;

    /* */
    subs_properties_t props;
Laurent Aimar's avatar
   
Laurent Aimar committed
185
186
};

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
static int  ParseMicroDvd   ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseSubRip     ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseSubViewer  ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseSSA        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseVplayer    ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseSami       ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseDVDSubtitle( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseMPL2       ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseAQT        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParsePJS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseMPSub      ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseJSS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParsePSB        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseRealText   ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseDKS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseSubViewer1 ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
static int  ParseCommonVTTSBV( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
204

205
static const struct
206
{
hartman's avatar
hartman committed
207
    const char *psz_type_name;
208
    int  i_type;
hartman's avatar
hartman committed
209
    const char *psz_name;
210
    int  (*pf_read)( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t*, size_t );
211
212
} sub_read_subtitle_function [] =
{
213
214
215
216
217
218
219
220
221
    { "microdvd",   SUB_TYPE_MICRODVD,    "MicroDVD",    ParseMicroDvd },
    { "subrip",     SUB_TYPE_SUBRIP,      "SubRIP",      ParseSubRip },
    { "subviewer",  SUB_TYPE_SUBVIEWER,   "SubViewer",   ParseSubViewer },
    { "ssa1",       SUB_TYPE_SSA1,        "SSA-1",       ParseSSA },
    { "ssa2-4",     SUB_TYPE_SSA2_4,      "SSA-2/3/4",   ParseSSA },
    { "ass",        SUB_TYPE_ASS,         "SSA/ASS",     ParseSSA },
    { "vplayer",    SUB_TYPE_VPLAYER,     "VPlayer",     ParseVplayer },
    { "sami",       SUB_TYPE_SAMI,        "SAMI",        ParseSami },
    { "dvdsubtitle",SUB_TYPE_DVDSUBTITLE, "DVDSubtitle", ParseDVDSubtitle },
222
    { "mpl2",       SUB_TYPE_MPL2,        "MPL2",        ParseMPL2 },
223
    { "aqt",        SUB_TYPE_AQT,         "AQTitle",     ParseAQT },
224
    { "pjs",        SUB_TYPE_PJS,         "PhoenixSub",  ParsePJS },
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
225
    { "mpsub",      SUB_TYPE_MPSUB,       "MPSub",       ParseMPSub },
226
    { "jacosub",    SUB_TYPE_JACOSUB,     "JacoSub",     ParseJSS },
227
    { "psb",        SUB_TYPE_PSB,         "PowerDivx",   ParsePSB },
228
    { "realtext",   SUB_TYPE_RT,          "RealText",    ParseRealText },
229
    { "dks",        SUB_TYPE_DKS,         "DKS",         ParseDKS },
230
    { "subviewer1", SUB_TYPE_SUBVIEW1,    "Subviewer 1", ParseSubViewer1 },
231
232
    { "text/vtt",   SUB_TYPE_VTT,         "WebVTT",      ParseCommonVTTSBV },
    { "sbv",        SUB_TYPE_SBV,         "SBV",         ParseCommonVTTSBV },
233
    { NULL,         SUB_TYPE_UNKNOWN,     "Unknown",     NULL }
234
};
235
236
237
/* When adding support for more formats, be sure to add their file extension
 * to src/input/subtitles.c to enable auto-detection.
 */
238

Laurent Aimar's avatar
   
Laurent Aimar committed
239
240
241
static int Demux( demux_t * );
static int Control( demux_t *, int, va_list );

242
static void Fix( demux_t * );
243
static char * get_language_from_filename( const char * );
Laurent Aimar's avatar
   
Laurent Aimar committed
244

245
/*****************************************************************************
Laurent Aimar's avatar
   
Laurent Aimar committed
246
 * Module initializer
247
 *****************************************************************************/
Laurent Aimar's avatar
   
Laurent Aimar committed
248
static int Open ( vlc_object_t *p_this )
249
{
250
251
252
253
254
    demux_t        *p_demux = (demux_t*)p_this;
    demux_sys_t    *p_sys;
    es_format_t    fmt;
    float          f_fps;
    char           *psz_type;
255
    int  (*pf_read)( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t*, size_t );
256
    int            i;
Eric Petit's avatar
Eric Petit committed
257

258
    if( !p_demux->obj.force )
259
    {
Laurent Aimar's avatar
   
Laurent Aimar committed
260
        msg_Dbg( p_demux, "subtitle demux discarded" );
261
262
        return VLC_EGENERIC;
    }
gbazin's avatar
   
gbazin committed
263

Laurent Aimar's avatar
   
Laurent Aimar committed
264
265
266
    p_demux->pf_demux = Demux;
    p_demux->pf_control = Control;
    p_demux->p_sys = p_sys = malloc( sizeof( demux_sys_t ) );
267
268
269
    if( p_sys == NULL )
        return VLC_ENOMEM;

270
271
272
    p_sys->subtitles.i_current= 0;
    p_sys->subtitles.i_count  = 0;
    p_sys->subtitles.p_array  = NULL;
Laurent Aimar's avatar
   
Laurent Aimar committed
273

274
275
276
277
    p_sys->props.psz_header         = NULL;
    p_sys->props.i_microsecperframe = 40000;
    p_sys->props.jss.b_inited       = false;
    p_sys->props.mpsub.b_inited     = false;
278

Laurent Aimar's avatar
   
Laurent Aimar committed
279
    /* Get the FPS */
280
    f_fps = var_CreateGetFloat( p_demux, "sub-original-fps" ); /* FIXME */
281
    if( f_fps >= 1.f )
282
        p_sys->props.i_microsecperframe = llroundf( 1000000.f / f_fps );
283

284
    msg_Dbg( p_demux, "Movie fps: %f", (double) f_fps );
zorglub's avatar
zorglub committed
285

hartman's avatar
hartman committed
286
287
    /* Check for override of the fps */
    f_fps = var_CreateGetFloat( p_demux, "sub-fps" );
288
    if( f_fps >= 1.f )
hartman's avatar
hartman committed
289
    {
290
        p_sys->props.i_microsecperframe = llroundf( 1000000.f / f_fps );
291
        msg_Dbg( p_demux, "Override subtitle fps %f", (double) f_fps );
hartman's avatar
hartman committed
292
293
    }

Laurent Aimar's avatar
   
Laurent Aimar committed
294
    /* Get or probe the type */
295
    p_sys->props.i_type = SUB_TYPE_UNKNOWN;
Laurent Aimar's avatar
   
Laurent Aimar committed
296
    psz_type = var_CreateGetString( p_demux, "sub-type" );
ivoire's avatar
ivoire committed
297
    if( psz_type && *psz_type )
298
    {
299
300
301
        int i;

        for( i = 0; ; i++ )
302
        {
303
304
            if( sub_read_subtitle_function[i].psz_type_name == NULL )
                break;
Laurent Aimar's avatar
   
Laurent Aimar committed
305

306
            if( !strcmp( sub_read_subtitle_function[i].psz_type_name,
Laurent Aimar's avatar
   
Laurent Aimar committed
307
                         psz_type ) )
308
            {
309
                p_sys->props.i_type = sub_read_subtitle_function[i].i_type;
310
311
                break;
            }
312
313
        }
    }
Laurent Aimar's avatar
   
Laurent Aimar committed
314
    free( psz_type );
Laurent Aimar's avatar
Laurent Aimar committed
315

316
#ifndef NDEBUG
317
    const uint64_t i_start_pos = vlc_stream_Tell( p_demux->s );
318
319
320
#endif
    uint64_t i_read_offset = 0;

321
322
323
    /* Detect Unicode while skipping the UTF-8 Byte Order Mark */
    bool unicode = false;
    const uint8_t *p_data;
324
    if( vlc_stream_Peek( p_demux->s, &p_data, 3 ) >= 3
325
326
327
     && !memcmp( p_data, "\xEF\xBB\xBF", 3 ) )
    {
        unicode = true;
328
        i_read_offset = 3; /* skip BOM */
329
330
331
        msg_Dbg( p_demux, "detected Unicode Byte Order Mark" );
    }

Laurent Aimar's avatar
   
Laurent Aimar committed
332
    /* Probe if unknown type */
333
    if( p_sys->props.i_type == SUB_TYPE_UNKNOWN )
334
    {
335
        int     i_try;
336
        char    *s = NULL;
337

Laurent Aimar's avatar
   
Laurent Aimar committed
338
339
        msg_Dbg( p_demux, "autodetecting subtitle format" );
        for( i_try = 0; i_try < 256; i_try++ )
340
341
        {
            int i_dummy;
342
            char p_dummy;
343

344
            if( (s = peek_Readline( p_demux->s, &i_read_offset )) == NULL )
345
346
                break;

347
            if( strcasestr( s, "<SAMI>" ) )
348
            {
349
                p_sys->props.i_type = SUB_TYPE_SAMI;
350
351
352
353
                break;
            }
            else if( sscanf( s, "{%d}{%d}", &i_dummy, &i_dummy ) == 2 ||
                     sscanf( s, "{%d}{}", &i_dummy ) == 1)
354
            {
355
                p_sys->props.i_type = SUB_TYPE_MICRODVD;
356
357
                break;
            }
358
            else if( sscanf( s, "%d:%d:%d,%d --> %d:%d:%d,%d",
Laurent Aimar's avatar
Laurent Aimar committed
359
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
360
361
362
363
364
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy ) == 8 ||
                     sscanf( s, "%d:%d:%d --> %d:%d:%d,%d",
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
                     sscanf( s, "%d:%d:%d,%d --> %d:%d:%d",
365
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
366
367
368
369
370
371
372
373
374
375
376
377
378
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
                     sscanf( s, "%d:%d:%d.%d --> %d:%d:%d.%d",
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy ) == 8 ||
                     sscanf( s, "%d:%d:%d --> %d:%d:%d.%d",
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
                     sscanf( s, "%d:%d:%d.%d --> %d:%d:%d",
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
                     sscanf( s, "%d:%d:%d --> %d:%d:%d",
                             &i_dummy,&i_dummy,&i_dummy,
                             &i_dummy,&i_dummy,&i_dummy ) == 6 )
379
            {
380
                p_sys->props.i_type = SUB_TYPE_SUBRIP;
381
382
                break;
            }
383
            else if( !strncasecmp( s, "!: This is a Sub Station Alpha v1", 33 ) )
384
            {
385
                p_sys->props.i_type = SUB_TYPE_SSA1;
386
                break;
387
            }
388
            else if( !strncasecmp( s, "ScriptType: v4.00+", 18 ) )
389
            {
390
                p_sys->props.i_type = SUB_TYPE_ASS;
391
392
393
394
                break;
            }
            else if( !strncasecmp( s, "ScriptType: v4.00", 17 ) )
            {
395
                p_sys->props.i_type = SUB_TYPE_SSA2_4;
396
                break;
397
            }
398
            else if( !strncasecmp( s, "Dialogue: Marked", 16  ) )
399
            {
400
                p_sys->props.i_type = SUB_TYPE_SSA2_4;
401
402
403
404
                break;
            }
            else if( !strncasecmp( s, "Dialogue:", 9  ) )
            {
405
                p_sys->props.i_type = SUB_TYPE_ASS;
406
407
                break;
            }
408
            else if( strcasestr( s, "[INFORMATION]" ) )
409
            {
410
                p_sys->props.i_type = SUB_TYPE_SUBVIEWER; /* I hope this will work */
411
412
                break;
            }
413
414
415
416
            else if( sscanf( s, "%d:%d:%d.%d %d:%d:%d",
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy,
                                 &i_dummy, &i_dummy, &i_dummy ) == 7 ||
                     sscanf( s, "@%d @%d", &i_dummy, &i_dummy) == 2)
417
            {
418
                p_sys->props.i_type = SUB_TYPE_JACOSUB;
419
                break;
420
            }
421
422
423
424
            else if( sscanf( s, "%d:%d:%d.%d,%d:%d:%d.%d",
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy,
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy ) == 8 )
            {
425
                p_sys->props.i_type = SUB_TYPE_SBV;
426
427
                break;
            }
428
429
            else if( sscanf( s, "%d:%d:%d:", &i_dummy, &i_dummy, &i_dummy ) == 3 ||
                     sscanf( s, "%d:%d:%d ", &i_dummy, &i_dummy, &i_dummy ) == 3 )
430
            {
431
                p_sys->props.i_type = SUB_TYPE_VPLAYER;
432
                break;
433
            }
434
435
436
            else if( sscanf( s, "{T %d:%d:%d:%d", &i_dummy, &i_dummy,
                             &i_dummy, &i_dummy ) == 4 )
            {
437
                p_sys->props.i_type = SUB_TYPE_DVDSUBTITLE;
438
439
                break;
            }
440
441
442
            else if( sscanf( s, "[%d:%d:%d]%c",
                     &i_dummy, &i_dummy, &i_dummy, &p_dummy ) == 4 )
            {
443
                p_sys->props.i_type = SUB_TYPE_DKS;
444
445
446
447
                break;
            }
            else if( strstr( s, "*** START SCRIPT" ) )
            {
448
                p_sys->props.i_type = SUB_TYPE_SUBVIEW1;
449
450
                break;
            }
451
452
453
            else if( sscanf( s, "[%d][%d]", &i_dummy, &i_dummy ) == 2 ||
                     sscanf( s, "[%d][]", &i_dummy ) == 1)
            {
454
                p_sys->props.i_type = SUB_TYPE_MPL2;
455
                break;
456
            }
457
458
459
            else if( sscanf (s, "FORMAT=%d", &i_dummy) == 1 ||
                     ( sscanf (s, "FORMAT=TIM%c", &p_dummy) == 1
                       && p_dummy =='E' ) )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
460
            {
461
                p_sys->props.i_type = SUB_TYPE_MPSUB;
462
                break;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
463
            }
464
            else if( sscanf( s, "-->> %d", &i_dummy) == 1 )
465
            {
466
                p_sys->props.i_type = SUB_TYPE_AQT;
467
                break;
468
            }
469
470
            else if( sscanf( s, "%d,%d,", &i_dummy, &i_dummy ) == 2 )
            {
471
                p_sys->props.i_type = SUB_TYPE_PJS;
472
                break;
473
            }
474
475
            else if( sscanf( s, "{%d:%d:%d}",
                                &i_dummy, &i_dummy, &i_dummy ) == 3 )
476
            {
477
                p_sys->props.i_type = SUB_TYPE_PSB;
478
                break;
479
            }
480
481
            else if( strcasestr( s, "<time" ) )
            {
482
                p_sys->props.i_type = SUB_TYPE_RT;
483
                break;
484
            }
Arun Pandian G's avatar
Arun Pandian G committed
485
486
            else if( !strncasecmp( s, "WEBVTT",6 ) )
            {
487
                p_sys->props.i_type = SUB_TYPE_VTT;
Arun Pandian G's avatar
Arun Pandian G committed
488
489
                break;
            }
490
491
492

            free( s );
            s = NULL;
493
        }
494

495
        free( s );
Laurent Aimar's avatar
   
Laurent Aimar committed
496
    }
497
498

    /* Quit on unknown subtitles */
499
    if( p_sys->props.i_type == SUB_TYPE_UNKNOWN )
Laurent Aimar's avatar
   
Laurent Aimar committed
500
    {
501
502
#ifndef NDEBUG
        /* Ensure it will work with non seekable streams */
503
        assert( i_start_pos == vlc_stream_Tell( p_demux->s ) );
504
#endif
505
        msg_Warn( p_demux, "failed to recognize subtitle type" );
506
        free( p_sys );
Laurent Aimar's avatar
   
Laurent Aimar committed
507
        return VLC_EGENERIC;
508
509
    }

510
    for( i = 0; ; i++ )
511
    {
512
        if( sub_read_subtitle_function[i].i_type == p_sys->props.i_type )
513
        {
Laurent Aimar's avatar
   
Laurent Aimar committed
514
515
516
            msg_Dbg( p_demux, "detected %s format",
                     sub_read_subtitle_function[i].psz_name );
            pf_read = sub_read_subtitle_function[i].pf_read;
517
518
            break;
        }
519
    }
520

Laurent Aimar's avatar
   
Laurent Aimar committed
521
522
    msg_Dbg( p_demux, "loading all subtitles..." );

523
    if( unicode ) /* skip BOM */
524
        vlc_stream_Seek( p_demux->s, 3 );
525

Laurent Aimar's avatar
   
Laurent Aimar committed
526
527
528
529
    /* Load the whole file */
    TextLoad( &p_sys->txt, p_demux->s );

    /* Parse it */
530
    for( size_t i_max = 0; i_max < SIZE_MAX - 500 * sizeof(subtitle_t); )
Laurent Aimar's avatar
Laurent Aimar committed
531
    {
532
        if( p_sys->subtitles.i_count >= i_max )
533
        {
Laurent Aimar's avatar
   
Laurent Aimar committed
534
            i_max += 500;
535
536
            subtitle_t *p_realloc = realloc( p_sys->subtitles.p_array, sizeof(subtitle_t) * i_max );
            if( p_realloc == NULL )
537
            {
538
                TextUnload( &p_sys->txt );
539
                Close( p_this );
gbazin's avatar
   
gbazin committed
540
                return VLC_ENOMEM;
541
            }
542
            p_sys->subtitles.p_array = p_realloc;
543
        }
Laurent Aimar's avatar
   
Laurent Aimar committed
544

545
546
        if( pf_read( VLC_OBJECT(p_demux), &p_sys->props, &p_sys->txt,
                     &p_sys->subtitles.p_array[p_sys->subtitles.i_count],
547
                     p_sys->subtitles.i_count ) )
548
            break;
Laurent Aimar's avatar
   
Laurent Aimar committed
549

550
        p_sys->subtitles.i_count++;
551
    }
Laurent Aimar's avatar
   
Laurent Aimar committed
552
553
    /* Unload */
    TextUnload( &p_sys->txt );
554

555
    msg_Dbg(p_demux, "loaded %zu subtitles", p_sys->subtitles.i_count );
556

Laurent Aimar's avatar
   
Laurent Aimar committed
557
    /* Fix subtitle (order and time) *** */
558
    p_sys->subtitles.i_current = 0;
Laurent Aimar's avatar
   
Laurent Aimar committed
559
    p_sys->i_length = 0;
560
    if( p_sys->subtitles.i_count > 0 )
561
    {
562
        p_sys->i_length = p_sys->subtitles.p_array[p_sys->subtitles.i_count-1].i_stop;
Laurent Aimar's avatar
   
Laurent Aimar committed
563
564
        /* +1 to avoid 0 */
        if( p_sys->i_length <= 0 )
565
            p_sys->i_length = p_sys->subtitles.p_array[p_sys->subtitles.i_count-1].i_start+1;
566
    }
Laurent Aimar's avatar
Laurent Aimar committed
567

568
    /* *** add subtitle ES *** */
569
570
571
    if( p_sys->props.i_type == SUB_TYPE_SSA1 ||
             p_sys->props.i_type == SUB_TYPE_SSA2_4 ||
             p_sys->props.i_type == SUB_TYPE_ASS )
572
    {
573
        Fix( p_demux );
574
        es_format_Init( &fmt, SPU_ES, VLC_CODEC_SSA );
575
576
    }
    else
577
        es_format_Init( &fmt, SPU_ES, VLC_CODEC_SUBT );
578
579
580
581
582
583
584
585
586
587
588

    /* Stupid language detection in the filename */
    char * psz_language = get_language_from_filename( p_demux->psz_file );

    if( psz_language )
    {
        fmt.psz_language = psz_language;
        msg_Dbg( p_demux, "detected language %s of subtitle: %s", psz_language,
                 p_demux->psz_location );
    }

589
590
    if( unicode )
        fmt.subs.psz_encoding = strdup( "UTF-8" );
591
592
593
594
595
    char *psz_description = var_InheritString( p_demux, "sub-description" );
    if( psz_description && *psz_description )
        fmt.psz_description = psz_description;
    else
        free( psz_description );
596
    if( p_sys->props.psz_header != NULL )
597
    {
598
599
        fmt.i_extra = strlen( p_sys->props.psz_header ) + 1;
        fmt.p_extra = strdup( p_sys->props.psz_header );
600
    }
Laurent Aimar's avatar
   
Laurent Aimar committed
601
    p_sys->es = es_out_Add( p_demux->out, &fmt );
602
    es_format_Clean( &fmt );
hartman's avatar
hartman committed
603

Laurent Aimar's avatar
Laurent Aimar committed
604
    return VLC_SUCCESS;
605
606
607
}

/*****************************************************************************
Laurent Aimar's avatar
   
Laurent Aimar committed
608
 * Close: Close subtitle demux
609
 *****************************************************************************/
Laurent Aimar's avatar
   
Laurent Aimar committed
610
static void Close( vlc_object_t *p_this )
611
{
Laurent Aimar's avatar
   
Laurent Aimar committed
612
613
    demux_t *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys = p_demux->p_sys;
614

615
616
617
    for( size_t i = 0; i < p_sys->subtitles.i_count; i++ )
        free( p_sys->subtitles.p_array[i].psz_text );
    free( p_sys->subtitles.p_array );
618
    free( p_sys->props.psz_header );
Laurent Aimar's avatar
   
Laurent Aimar committed
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633

    free( p_sys );
}

/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( demux_t *p_demux, int i_query, va_list args )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    int64_t *pi64, i64;
    double *pf, f;

    switch( i_query )
    {
634
635
636
637
        case DEMUX_CAN_SEEK:
            *va_arg( args, bool * ) = true;
            return VLC_SUCCESS;

Laurent Aimar's avatar
   
Laurent Aimar committed
638
639
640
641
642
643
644
        case DEMUX_GET_LENGTH:
            pi64 = (int64_t*)va_arg( args, int64_t * );
            *pi64 = p_sys->i_length;
            return VLC_SUCCESS;

        case DEMUX_GET_TIME:
            pi64 = (int64_t*)va_arg( args, int64_t * );
645
            if( p_sys->subtitles.i_current < p_sys->subtitles.i_count )
Laurent Aimar's avatar
   
Laurent Aimar committed
646
            {
647
                *pi64 = p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start;
Laurent Aimar's avatar
   
Laurent Aimar committed
648
649
650
651
652
653
                return VLC_SUCCESS;
            }
            return VLC_EGENERIC;

        case DEMUX_SET_TIME:
            i64 = (int64_t)va_arg( args, int64_t );
654
655
            p_sys->subtitles.i_current = 0;
            while( p_sys->subtitles.i_current < p_sys->subtitles.i_count )
Laurent Aimar's avatar
   
Laurent Aimar committed
656
            {
657
                const subtitle_t *p_subtitle = &p_sys->subtitles.p_array[p_sys->subtitles.i_current];
658
659
660
661
662
663

                if( p_subtitle->i_start > i64 )
                    break;
                if( p_subtitle->i_stop > p_subtitle->i_start && p_subtitle->i_stop > i64 )
                    break;

664
                p_sys->subtitles.i_current++;
Laurent Aimar's avatar
   
Laurent Aimar committed
665
            }
666

667
            if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
Laurent Aimar's avatar
   
Laurent Aimar committed
668
669
670
671
672
                return VLC_EGENERIC;
            return VLC_SUCCESS;

        case DEMUX_GET_POSITION:
            pf = (double*)va_arg( args, double * );
673
            if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
Laurent Aimar's avatar
   
Laurent Aimar committed
674
675
676
            {
                *pf = 1.0;
            }
677
            else if( p_sys->subtitles.i_count > 0 )
Laurent Aimar's avatar
   
Laurent Aimar committed
678
            {
679
                *pf = (double)p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start /
Laurent Aimar's avatar
   
Laurent Aimar committed
680
681
682
683
684
685
686
687
688
689
690
691
                      (double)p_sys->i_length;
            }
            else
            {
                *pf = 0.0;
            }
            return VLC_SUCCESS;

        case DEMUX_SET_POSITION:
            f = (double)va_arg( args, double );
            i64 = f * p_sys->i_length;

692
693
694
            p_sys->subtitles.i_current = 0;
            while( p_sys->subtitles.i_current < p_sys->subtitles.i_count &&
                   p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start < i64 )
Laurent Aimar's avatar
   
Laurent Aimar committed
695
            {
696
                p_sys->subtitles.i_current++;
Laurent Aimar's avatar
   
Laurent Aimar committed
697
            }
698
            if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
Laurent Aimar's avatar
   
Laurent Aimar committed
699
700
701
702
703
704
705
                return VLC_EGENERIC;
            return VLC_SUCCESS;

        case DEMUX_SET_NEXT_DEMUX_TIME:
            p_sys->i_next_demux_date = (int64_t)va_arg( args, int64_t );
            return VLC_SUCCESS;

706
        case DEMUX_GET_PTS_DELAY:
Laurent Aimar's avatar
   
Laurent Aimar committed
707
708
        case DEMUX_GET_FPS:
        case DEMUX_GET_META:
709
        case DEMUX_GET_ATTACHMENTS:
Laurent Aimar's avatar
   
Laurent Aimar committed
710
        case DEMUX_GET_TITLE_INFO:
711
        case DEMUX_HAS_UNSUPPORTED_META:
Laurent Aimar's avatar
Laurent Aimar committed
712
        case DEMUX_CAN_RECORD:
Laurent Aimar's avatar
   
Laurent Aimar committed
713
714
715
            return VLC_EGENERIC;

        default:
716
            msg_Err( p_demux, "unknown query %d in subtitle control", i_query );
Laurent Aimar's avatar
   
Laurent Aimar committed
717
            return VLC_EGENERIC;
718
    }
Laurent Aimar's avatar
   
Laurent Aimar committed
719
720
721
722
723
724
725
726
727
728
}

/*****************************************************************************
 * Demux: Send subtitle to decoder
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    int64_t i_maxdate;

729
    if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
730
        return VLC_DEMUXER_EOF;
Laurent Aimar's avatar
   
Laurent Aimar committed
731

732
    i_maxdate = p_sys->i_next_demux_date - var_GetInteger( p_demux->obj.parent, "spu-delay" );;
733
    if( i_maxdate <= 0 && p_sys->subtitles.i_current < p_sys->subtitles.i_count )
734
    {
Laurent Aimar's avatar
   
Laurent Aimar committed
735
        /* Should not happen */
736
        i_maxdate = p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start + 1;
737
738
    }

739
740
    while( p_sys->subtitles.i_current < p_sys->subtitles.i_count &&
           p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start < i_maxdate )
741
    {
742
        const subtitle_t *p_subtitle = &p_sys->subtitles.p_array[p_sys->subtitles.i_current];
743

hartman's avatar
hartman committed
744
        block_t *p_block;
745
        int i_len = strlen( p_subtitle->psz_text ) + 1;
746

747
        if( i_len <= 1 || p_subtitle->i_start < 0 )
hartman's avatar
hartman committed
748
        {
749
            p_sys->subtitles.i_current++;
hartman's avatar
hartman committed
750
            continue;
751
        }
752

753
        if( ( p_block = block_Alloc( i_len ) ) == NULL )
hartman's avatar
hartman committed
754
        {
755
            p_sys->subtitles.i_current++;
hartman's avatar
hartman committed
756
757
            continue;
        }
758

759
        p_block->i_dts =
760
761
        p_block->i_pts = VLC_TS_0 + p_subtitle->i_start;
        if( p_subtitle->i_stop >= 0 && p_subtitle->i_stop >= p_subtitle->i_start )
762
            p_block->i_length = p_subtitle->i_stop - p_subtitle->i_start;
763

764
765
766
        memcpy( p_block->p_buffer, p_subtitle->psz_text, i_len );

        es_out_Send( p_demux->out, p_sys->es, p_block );
767

768
        p_sys->subtitles.i_current++;
769
770
    }

Laurent Aimar's avatar
   
Laurent Aimar committed
771
772
    /* */
    p_sys->i_next_demux_date = 0;
773

774
    return VLC_DEMUXER_SUCCESS;
775
}
776

777
778
779

static int subtitle_cmp( const void *first, const void *second )
{
780
781
782
783
    int64_t result = ((subtitle_t *)(first))->i_start - ((subtitle_t *)(second))->i_start;
    /* Return -1, 0 ,1, and not directly substraction
     * as result can be > INT_MAX */
    return result == 0 ? 0 : result > 0 ? 1 : -1;
784
}
785
/*****************************************************************************
Laurent Aimar's avatar
   
Laurent Aimar committed
786
 * Fix: fix time stamp and order of subtitle
787
 *****************************************************************************/
Laurent Aimar's avatar
   
Laurent Aimar committed
788
static void Fix( demux_t *p_demux )
789
{
Laurent Aimar's avatar
   
Laurent Aimar committed
790
    demux_sys_t *p_sys = p_demux->p_sys;
791
792

    /* *** fix order (to be sure...) *** */
793
    qsort( p_sys->subtitles.p_array, p_sys->subtitles.i_count, sizeof( p_sys->subtitles.p_array[0] ), subtitle_cmp);
Laurent Aimar's avatar
   
Laurent Aimar committed
794
795
796
797
798
799
800
801
802
803
804
}

static int TextLoad( text_t *txt, stream_t *s )
{
    int   i_line_max;

    /* init txt */
    i_line_max          = 500;
    txt->i_line_count   = 0;
    txt->i_line         = 0;
    txt->line           = calloc( i_line_max, sizeof( char * ) );
805
806
    if( !txt->line )
        return VLC_ENOMEM;
Laurent Aimar's avatar
   
Laurent Aimar committed
807
808
809
810

    /* load the complete file */
    for( ;; )
    {
811
        char *psz = vlc_stream_ReadLine( s );
Laurent Aimar's avatar
   
Laurent Aimar committed
812
813
814
815
816
817
818
819

        if( psz == NULL )
            break;

        txt->line[txt->i_line_count++] = psz;
        if( txt->i_line_count >= i_line_max )
        {
            i_line_max += 100;
820
821
822
            txt->line = realloc_or_free( txt->line, i_line_max * sizeof( char * ) );
            if( !txt->line )
                return VLC_ENOMEM;