Coverage Report

Created: 2025-08-29 06:30

/src/vlc/modules/demux/subtitle.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * subtitle.c: Demux for subtitle text files.
3
 *****************************************************************************
4
 * Copyright (C) 1999-2007 VLC authors and VideoLAN
5
 * Copyright (C) 2023      Videolabs
6
 *
7
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8
 *          Derk-Jan Hartman <hartman at videolan dot org>
9
 *          Jean-Baptiste Kempf <jb@videolan.org>
10
 *          Alexandre Janniaux <ajanni@videolabs.io>
11
 *
12
 * This program is free software; you can redistribute it and/or modify it
13
 * under the terms of the GNU Lesser General Public License as published by
14
 * the Free Software Foundation; either version 2.1 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Lesser General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Lesser General Public License
23
 * along with this program; if not, write to the Free Software Foundation,
24
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25
 *****************************************************************************/
26
27
/*****************************************************************************
28
 * Preamble
29
 *****************************************************************************/
30
31
#ifdef HAVE_CONFIG_H
32
# include "config.h"
33
#endif
34
35
#include <vlc_common.h>
36
#include <vlc_arrays.h>
37
#include <vlc_plugin.h>
38
39
#include <ctype.h>
40
#include <math.h>
41
#include <assert.h>
42
#include <stdckdint.h>
43
44
#include <vlc_demux.h>
45
#include <vlc_charset.h>
46
47
/*****************************************************************************
48
 * Module descriptor
49
 *****************************************************************************/
50
static int  Open ( vlc_object_t *p_this );
51
static void Close( vlc_object_t *p_this );
52
53
#define SUB_TYPE_LONGTEXT \
54
    N_("Force the subtitles format. Selecting \"auto\" means autodetection and should always work.")
55
#define SUB_DESCRIPTION_LONGTEXT \
56
    N_("Override the default track description.")
57
58
static const char *const ppsz_sub_type[] =
59
{
60
    "auto", "microdvd", "subrip", "subviewer", "ssa1",
61
    "ssa2-4", "ass", "vplayer", "sami", "dvdsubtitle", "mpl2",
62
    "aqt", "pjs", "mpsub", "jacosub", "psb", "realtext", "dks",
63
    "subviewer1", "sbv"
64
};
65
66
4
vlc_module_begin ()
67
2
    set_shortname( N_("Subtitles"))
68
2
    set_description( N_("Text subtitle parser") )
69
2
    set_capability( "demux", 0 )
70
2
    set_subcategory( SUBCAT_INPUT_DEMUX )
71
2
    add_string( "sub-type", "auto", N_("Subtitle format"),
72
2
                SUB_TYPE_LONGTEXT )
73
2
        change_string_list( ppsz_sub_type, ppsz_sub_type )
74
2
    add_string( "sub-description", NULL, N_("Subtitle description"),
75
2
                SUB_DESCRIPTION_LONGTEXT )
76
2
    set_callbacks( Open, Close )
77
78
2
    add_shortcut( "subtitle" )
79
2
vlc_module_end ()
80
81
/*****************************************************************************
82
 * Prototypes:
83
 *****************************************************************************/
84
enum subtitle_type_e
85
{
86
    SUB_TYPE_UNKNOWN = -1,
87
    SUB_TYPE_MICRODVD,
88
    SUB_TYPE_SUBRIP,
89
    SUB_TYPE_SSA1,
90
    SUB_TYPE_SSA2_4,
91
    SUB_TYPE_ASS,
92
    SUB_TYPE_VPLAYER,
93
    SUB_TYPE_SAMI,
94
    SUB_TYPE_SUBVIEWER, /* SUBVIEWER 2 */
95
    SUB_TYPE_DVDSUBTITLE, /* Mplayer calls it subviewer2 */
96
    SUB_TYPE_MPL2,
97
    SUB_TYPE_AQT,
98
    SUB_TYPE_PJS,
99
    SUB_TYPE_MPSUB,
100
    SUB_TYPE_JACOSUB,
101
    SUB_TYPE_PSB,
102
    SUB_TYPE_RT,
103
    SUB_TYPE_DKS,
104
    SUB_TYPE_SUBVIEW1, /* SUBVIEWER 1 - mplayer calls it subrip09,
105
                         and Gnome subtitles SubViewer 1.0 */
106
    SUB_TYPE_SBV,
107
    SUB_TYPE_SCC,      /* Scenarist Closed Caption */
108
};
109
110
typedef struct
111
{
112
    size_t  i_line_count;
113
    size_t  i_line;
114
    char    **line;
115
} text_t;
116
117
static int  TextLoad( text_t *, stream_t *s );
118
static void TextUnload( text_t * );
119
120
typedef struct
121
{
122
    vlc_tick_t i_start;
123
    vlc_tick_t i_stop;
124
125
    char    *psz_text;
126
} subtitle_t;
127
128
typedef struct
129
{
130
    enum subtitle_type_e i_type;
131
    vlc_tick_t  i_microsecperframe;
132
133
    char        *psz_header; /* SSA */
134
    char        *psz_lang;
135
136
    struct
137
    {
138
        bool b_inited;
139
140
        int i_comment;
141
        int i_time_resolution;
142
        int i_time_shift;
143
    } jss;
144
145
    struct
146
    {
147
        bool  b_inited;
148
149
        float f_total;
150
        int i_factor;
151
    } mpsub;
152
153
    struct
154
    {
155
        const char *psz_start;
156
    } sami;
157
158
} subs_properties_t;
159
160
typedef struct
161
{
162
    es_out_id_t *es;
163
    bool        b_slave;
164
    bool        b_first_time;
165
166
    double      f_rate;
167
    vlc_tick_t  i_next_demux_date;
168
169
    struct
170
    {
171
        subtitle_t *p_array;
172
        size_t      i_count;
173
        size_t      i_current;
174
    } subtitles;
175
176
    vlc_tick_t  i_length;
177
178
    /* */
179
    subs_properties_t props;
180
181
    block_t * (*pf_convert)( const subtitle_t * );
182
} demux_sys_t;
183
184
static int  ParseMicroDvd   ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
185
static int  ParseSubRip     ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
186
static int  ParseSubViewer  ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
187
static int  ParseSSA        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
188
static int  ParseVplayer    ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
189
static int  ParseSami       ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
190
static int  ParseDVDSubtitle( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
191
static int  ParseMPL2       ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
192
static int  ParseAQT        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
193
static int  ParsePJS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
194
static int  ParseMPSub      ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
195
static int  ParseJSS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
196
static int  ParsePSB        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
197
static int  ParseRealText   ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
198
static int  ParseDKS        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
199
static int  ParseSubViewer1 ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
200
static int  ParseCommonSBV  ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
201
static int  ParseSCC        ( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t *, size_t );
202
203
static const struct
204
{
205
    const char *psz_type_name;
206
    int  i_type;
207
    const char *psz_name;
208
    int  (*pf_read)( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t*, size_t );
209
} sub_read_subtitle_function [] =
210
{
211
    { "microdvd",   SUB_TYPE_MICRODVD,    "MicroDVD",    ParseMicroDvd },
212
    { "subrip",     SUB_TYPE_SUBRIP,      "SubRIP",      ParseSubRip },
213
    { "subviewer",  SUB_TYPE_SUBVIEWER,   "SubViewer",   ParseSubViewer },
214
    { "ssa1",       SUB_TYPE_SSA1,        "SSA-1",       ParseSSA },
215
    { "ssa2-4",     SUB_TYPE_SSA2_4,      "SSA-2/3/4",   ParseSSA },
216
    { "ass",        SUB_TYPE_ASS,         "SSA/ASS",     ParseSSA },
217
    { "vplayer",    SUB_TYPE_VPLAYER,     "VPlayer",     ParseVplayer },
218
    { "sami",       SUB_TYPE_SAMI,        "SAMI",        ParseSami },
219
    { "dvdsubtitle",SUB_TYPE_DVDSUBTITLE, "DVDSubtitle", ParseDVDSubtitle },
220
    { "mpl2",       SUB_TYPE_MPL2,        "MPL2",        ParseMPL2 },
221
    { "aqt",        SUB_TYPE_AQT,         "AQTitle",     ParseAQT },
222
    { "pjs",        SUB_TYPE_PJS,         "PhoenixSub",  ParsePJS },
223
    { "mpsub",      SUB_TYPE_MPSUB,       "MPSub",       ParseMPSub },
224
    { "jacosub",    SUB_TYPE_JACOSUB,     "JacoSub",     ParseJSS },
225
    { "psb",        SUB_TYPE_PSB,         "PowerDivx",   ParsePSB },
226
    { "realtext",   SUB_TYPE_RT,          "RealText",    ParseRealText },
227
    { "dks",        SUB_TYPE_DKS,         "DKS",         ParseDKS },
228
    { "subviewer1", SUB_TYPE_SUBVIEW1,    "Subviewer 1", ParseSubViewer1 },
229
    { "sbv",        SUB_TYPE_SBV,         "SBV",         ParseCommonSBV },
230
    { "scc",        SUB_TYPE_SCC,         "SCC",         ParseSCC },
231
    { NULL,         SUB_TYPE_UNKNOWN,     "Unknown",     NULL }
232
};
233
/* When adding support for more formats, be sure to add their file extension
234
 * to src/input/subtitles.c to enable auto-detection.
235
 */
236
237
static int Demux( demux_t * );
238
static int Control( demux_t *, int, va_list );
239
240
static void Fix( demux_t * );
241
static char * get_language_from_filename( const char * );
242
243
/*****************************************************************************
244
 * Decoder format output function
245
 *****************************************************************************/
246
247
static block_t *ToTextBlock( const subtitle_t *p_subtitle )
248
0
{
249
0
    if ( p_subtitle->psz_text == NULL )
250
0
        return NULL;
251
252
0
    block_t *p_block;
253
0
    size_t i_len = strlen( p_subtitle->psz_text ) + 1;
254
255
0
    if( i_len <= 1 || !(p_block = block_Alloc( i_len )) )
256
0
        return NULL;
257
258
0
    memcpy( p_block->p_buffer, p_subtitle->psz_text, i_len );
259
260
0
    return p_block;
261
0
}
262
263
static block_t *ToEIA608Block( const subtitle_t *p_subtitle )
264
0
{
265
0
    if ( p_subtitle->psz_text == NULL )
266
0
        return NULL;
267
268
0
    block_t *p_block;
269
0
    const size_t i_len = strlen( p_subtitle->psz_text );
270
0
    const size_t i_block = (1 + i_len / 5) * 3;
271
272
0
    if( i_len < 4 || !(p_block = block_Alloc( i_block )) )
273
0
        return NULL;
274
275
0
    p_block->i_buffer = 0;
276
277
0
    char *saveptr = NULL;
278
0
    char *psz_tok = strtok_r( p_subtitle->psz_text, " ", &saveptr );
279
0
    unsigned a, b;
280
0
    while( psz_tok &&
281
0
           sscanf( psz_tok, "%2x%2x", &a, &b ) == 2 &&
282
0
           i_block - p_block->i_buffer >= 3 )
283
0
    {
284
0
        uint8_t *p_data = &p_block->p_buffer[p_block->i_buffer];
285
0
        p_data[0] = 0xFC;
286
0
        p_data[1] = a;
287
0
        p_data[2] = b;
288
0
        p_block->i_buffer += 3;
289
0
        psz_tok = strtok_r( NULL, " ", &saveptr );
290
0
    }
291
292
0
    return p_block;
293
0
}
294
295
/*****************************************************************************
296
 * Module initializer
297
 *****************************************************************************/
298
static int Open ( vlc_object_t *p_this )
299
0
{
300
0
    demux_t        *p_demux = (demux_t*)p_this;
301
0
    demux_sys_t    *p_sys;
302
0
    es_format_t    fmt;
303
0
    float          f_fps;
304
0
    char           *psz_type;
305
0
    int  (*pf_read)( vlc_object_t *, subs_properties_t *, text_t *, subtitle_t*, size_t );
306
307
0
    if( !p_demux->obj.force )
308
0
    {
309
0
        msg_Dbg( p_demux, "subtitle demux discarded" );
310
0
        return VLC_EGENERIC;
311
0
    }
312
313
0
    p_demux->pf_demux = Demux;
314
0
    p_demux->pf_control = Control;
315
0
    p_demux->p_sys = p_sys = malloc( sizeof( demux_sys_t ) );
316
0
    if( p_sys == NULL )
317
0
        return VLC_ENOMEM;
318
319
0
    p_sys->b_slave = false;
320
0
    p_sys->b_first_time = true;
321
0
    p_sys->i_next_demux_date = 0;
322
0
    p_sys->f_rate = 1.0;
323
324
0
    p_sys->pf_convert = ToTextBlock;
325
326
0
    p_sys->subtitles.i_current= 0;
327
0
    p_sys->subtitles.i_count  = 0;
328
0
    p_sys->subtitles.p_array  = NULL;
329
330
0
    p_sys->props.psz_header         = NULL;
331
0
    p_sys->props.psz_lang           = NULL;
332
0
    p_sys->props.i_microsecperframe = VLC_TICK_FROM_MS(40);
333
0
    p_sys->props.jss.b_inited       = false;
334
0
    p_sys->props.mpsub.b_inited     = false;
335
0
    p_sys->props.sami.psz_start     = NULL;
336
337
    /* Get the FPS */
338
0
    f_fps = var_CreateGetFloat( p_demux, "sub-original-fps" );
339
0
    if( f_fps >= 1.f )
340
0
    {
341
0
        p_sys->props.i_microsecperframe = llroundf( (float)CLOCK_FREQ / f_fps );
342
0
        msg_Dbg( p_demux, "Override subtitle fps %f", (double) f_fps );
343
0
    }
344
345
    /* Get or probe the type */
346
0
    p_sys->props.i_type = SUB_TYPE_UNKNOWN;
347
0
    psz_type = var_CreateGetString( p_demux, "sub-type" );
348
0
    if( psz_type && *psz_type )
349
0
    {
350
0
        for( int i = 0; ; i++ )
351
0
        {
352
0
            if( sub_read_subtitle_function[i].psz_type_name == NULL )
353
0
                break;
354
355
0
            if( !strcmp( sub_read_subtitle_function[i].psz_type_name,
356
0
                         psz_type ) )
357
0
            {
358
0
                p_sys->props.i_type = sub_read_subtitle_function[i].i_type;
359
0
                break;
360
0
            }
361
0
        }
362
0
    }
363
0
    free( psz_type );
364
365
0
#ifndef NDEBUG
366
0
    const uint64_t i_start_pos = vlc_stream_Tell( p_demux->s );
367
0
#endif
368
369
0
    size_t i_peek;
370
0
    const uint8_t *p_peek;
371
0
    if( vlc_stream_Peek( p_demux->s, &p_peek, 16 ) < 16 )
372
0
    {
373
0
        free( p_sys );
374
0
        return VLC_EGENERIC;
375
0
    }
376
377
0
    enum
378
0
    {
379
0
        UTF8BOM,
380
0
        UTF16LE,
381
0
        UTF16BE,
382
0
        NOBOM,
383
0
    } e_bom = NOBOM;
384
0
    const char *psz_bom = NULL;
385
386
0
    i_peek = 4096;
387
    /* Detect Unicode while skipping the UTF-8 Byte Order Mark */
388
0
    if( !memcmp( p_peek, "\xEF\xBB\xBF", 3 ) )
389
0
    {
390
0
        e_bom = UTF8BOM;
391
0
        psz_bom = "UTF-8";
392
0
    }
393
0
    else if( !memcmp( p_peek, "\xFF\xFE", 2 ) )
394
0
    {
395
0
        e_bom = UTF16LE;
396
0
        psz_bom = "UTF-16LE";
397
0
        i_peek *= 2;
398
0
    }
399
0
    else if( !memcmp( p_peek, "\xFE\xFF", 2 ) )
400
0
    {
401
0
        e_bom = UTF16BE;
402
0
        psz_bom = "UTF-16BE";
403
0
        i_peek *= 2;
404
0
    }
405
406
0
    if( e_bom != NOBOM )
407
0
        msg_Dbg( p_demux, "detected %s Byte Order Mark", psz_bom );
408
409
0
    i_peek = vlc_stream_Peek( p_demux->s, &p_peek, i_peek );
410
0
    if( unlikely(i_peek < 16) )
411
0
    {
412
0
        free( p_sys );
413
0
        return VLC_EGENERIC;
414
0
    }
415
416
0
    stream_t *p_probestream = NULL;
417
0
    if( e_bom != UTF8BOM && e_bom != NOBOM )
418
0
    {
419
0
        if( i_peek > 16 )
420
0
        {
421
0
            char *p_outbuf = FromCharset( psz_bom, p_peek, i_peek );
422
0
            if( p_outbuf != NULL )
423
0
                p_probestream = vlc_stream_MemoryNew( p_demux, (uint8_t *)p_outbuf,
424
0
                                                      strlen( p_outbuf ),
425
0
                                                      false ); /* free p_outbuf on release */
426
0
        }
427
0
    }
428
0
    else
429
0
    {
430
0
        const size_t i_skip = (e_bom == UTF8BOM) ? 3 : 0;
431
0
        p_probestream = vlc_stream_MemoryNew( p_demux, (uint8_t *) &p_peek[i_skip],
432
0
                                              i_peek - i_skip, true );
433
0
    }
434
435
0
    if( p_probestream == NULL )
436
0
    {
437
0
        free( p_sys );
438
0
        return VLC_EGENERIC;
439
0
    }
440
441
    /* Probe if unknown type */
442
0
    if( p_sys->props.i_type == SUB_TYPE_UNKNOWN )
443
0
    {
444
0
        int     i_try;
445
0
        char    *s = NULL;
446
447
0
        msg_Dbg( p_demux, "autodetecting subtitle format" );
448
0
        for( i_try = 0; i_try < 256; i_try++ )
449
0
        {
450
0
            int i_dummy;
451
0
            char p_dummy;
452
453
0
            if( (s = vlc_stream_ReadLine( p_probestream ) ) == NULL )
454
0
                break;
455
456
0
            if( strcasestr( s, "<SAMI>" ) )
457
0
            {
458
0
                p_sys->props.i_type = SUB_TYPE_SAMI;
459
0
                break;
460
0
            }
461
0
            else if( sscanf( s, "{%d}{%d}", &i_dummy, &i_dummy ) == 2 ||
462
0
                     sscanf( s, "{%d}{}", &i_dummy ) == 1)
463
0
            {
464
0
                p_sys->props.i_type = SUB_TYPE_MICRODVD;
465
0
                break;
466
0
            }
467
0
            else if( sscanf( s, "%d:%d:%d,%d --> %d:%d:%d,%d",
468
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
469
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy ) == 8 ||
470
0
                     sscanf( s, "%d:%d:%d --> %d:%d:%d,%d",
471
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
472
0
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
473
0
                     sscanf( s, "%d:%d:%d,%d --> %d:%d:%d",
474
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
475
0
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
476
0
                     sscanf( s, "%d:%d:%d.%d --> %d:%d:%d.%d",
477
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
478
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy ) == 8 ||
479
0
                     sscanf( s, "%d:%d:%d --> %d:%d:%d.%d",
480
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
481
0
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
482
0
                     sscanf( s, "%d:%d:%d.%d --> %d:%d:%d",
483
0
                             &i_dummy,&i_dummy,&i_dummy,&i_dummy,
484
0
                             &i_dummy,&i_dummy,&i_dummy ) == 7 ||
485
0
                     sscanf( s, "%d:%d:%d --> %d:%d:%d",
486
0
                             &i_dummy,&i_dummy,&i_dummy,
487
0
                             &i_dummy,&i_dummy,&i_dummy ) == 6 )
488
0
            {
489
0
                p_sys->props.i_type = SUB_TYPE_SUBRIP;
490
0
                break;
491
0
            }
492
0
            else if( !strncasecmp( s, "!: This is a Sub Station Alpha v1", 33 ) )
493
0
            {
494
0
                p_sys->props.i_type = SUB_TYPE_SSA1;
495
0
                break;
496
0
            }
497
0
            else if( !strncasecmp( s, "ScriptType: v4.00+", 18 ) )
498
0
            {
499
0
                p_sys->props.i_type = SUB_TYPE_ASS;
500
0
                break;
501
0
            }
502
0
            else if( !strncasecmp( s, "ScriptType: v4.00", 17 ) )
503
0
            {
504
0
                p_sys->props.i_type = SUB_TYPE_SSA2_4;
505
0
                break;
506
0
            }
507
0
            else if( !strncasecmp( s, "Dialogue: Marked", 16  ) )
508
0
            {
509
0
                p_sys->props.i_type = SUB_TYPE_SSA2_4;
510
0
                break;
511
0
            }
512
0
            else if( !strncasecmp( s, "Dialogue:", 9  ) )
513
0
            {
514
0
                p_sys->props.i_type = SUB_TYPE_ASS;
515
0
                break;
516
0
            }
517
0
            else if( strcasestr( s, "[INFORMATION]" ) )
518
0
            {
519
0
                p_sys->props.i_type = SUB_TYPE_SUBVIEWER; /* I hope this will work */
520
0
                break;
521
0
            }
522
0
            else if( sscanf( s, "%d:%d:%d.%d %d:%d:%d",
523
0
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy,
524
0
                                 &i_dummy, &i_dummy, &i_dummy ) == 7 ||
525
0
                     sscanf( s, "@%d @%d", &i_dummy, &i_dummy) == 2)
526
0
            {
527
0
                p_sys->props.i_type = SUB_TYPE_JACOSUB;
528
0
                break;
529
0
            }
530
0
            else if( sscanf( s, "%d:%d:%d.%d,%d:%d:%d.%d",
531
0
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy,
532
0
                                 &i_dummy, &i_dummy, &i_dummy, &i_dummy ) == 8 )
533
0
            {
534
0
                p_sys->props.i_type = SUB_TYPE_SBV;
535
0
                break;
536
0
            }
537
0
            else if( sscanf( s, "%d:%d:%d:", &i_dummy, &i_dummy, &i_dummy ) == 3 ||
538
0
                     sscanf( s, "%d:%d:%d ", &i_dummy, &i_dummy, &i_dummy ) == 3 )
539
0
            {
540
0
                p_sys->props.i_type = SUB_TYPE_VPLAYER;
541
0
                break;
542
0
            }
543
0
            else if( sscanf( s, "{T %d:%d:%d:%d", &i_dummy, &i_dummy,
544
0
                             &i_dummy, &i_dummy ) == 4 )
545
0
            {
546
0
                p_sys->props.i_type = SUB_TYPE_DVDSUBTITLE;
547
0
                break;
548
0
            }
549
0
            else if( sscanf( s, "[%d:%d:%d]%c",
550
0
                     &i_dummy, &i_dummy, &i_dummy, &p_dummy ) == 4 )
551
0
            {
552
0
                p_sys->props.i_type = SUB_TYPE_DKS;
553
0
                break;
554
0
            }
555
0
            else if( strstr( s, "*** START SCRIPT" ) )
556
0
            {
557
0
                p_sys->props.i_type = SUB_TYPE_SUBVIEW1;
558
0
                break;
559
0
            }
560
0
            else if( sscanf( s, "[%d][%d]", &i_dummy, &i_dummy ) == 2 ||
561
0
                     sscanf( s, "[%d][]", &i_dummy ) == 1)
562
0
            {
563
0
                p_sys->props.i_type = SUB_TYPE_MPL2;
564
0
                break;
565
0
            }
566
0
            else if( sscanf (s, "FORMAT=%d", &i_dummy) == 1 ||
567
0
                     ( sscanf (s, "FORMAT=TIM%c", &p_dummy) == 1
568
0
                       && p_dummy =='E' ) )
569
0
            {
570
0
                p_sys->props.i_type = SUB_TYPE_MPSUB;
571
0
                break;
572
0
            }
573
0
            else if( sscanf( s, "-->> %d", &i_dummy) == 1 )
574
0
            {
575
0
                p_sys->props.i_type = SUB_TYPE_AQT;
576
0
                break;
577
0
            }
578
0
            else if( sscanf( s, "%d,%d,", &i_dummy, &i_dummy ) == 2 )
579
0
            {
580
0
                p_sys->props.i_type = SUB_TYPE_PJS;
581
0
                break;
582
0
            }
583
0
            else if( sscanf( s, "{%d:%d:%d}",
584
0
                                &i_dummy, &i_dummy, &i_dummy ) == 3 )
585
0
            {
586
0
                p_sys->props.i_type = SUB_TYPE_PSB;
587
0
                break;
588
0
            }
589
0
            else if( strcasestr( s, "<time" ) )
590
0
            {
591
0
                p_sys->props.i_type = SUB_TYPE_RT;
592
0
                break;
593
0
            }
594
0
            else if( !strncasecmp( s, "WEBVTT",6 ) )
595
0
            {
596
                /* FAIL */
597
0
                break;
598
0
            }
599
0
            else if( !strncasecmp( s, "Scenarist_SCC V1.0", 18 ) )
600
0
            {
601
0
                p_sys->props.i_type = SUB_TYPE_SCC;
602
0
                p_sys->pf_convert = ToEIA608Block;
603
0
                break;
604
0
            }
605
606
0
            free( s );
607
0
            s = NULL;
608
0
        }
609
610
0
        free( s );
611
0
    }
612
613
0
    vlc_stream_Delete( p_probestream );
614
615
    /* Quit on unknown subtitles */
616
0
    if( p_sys->props.i_type == SUB_TYPE_UNKNOWN )
617
0
    {
618
0
#ifndef NDEBUG
619
        /* Ensure it will work with non seekable streams */
620
0
        assert( i_start_pos == vlc_stream_Tell( p_demux->s ) );
621
0
#endif
622
0
        msg_Warn( p_demux, "failed to recognize subtitle type" );
623
0
        free( p_sys );
624
0
        return VLC_EGENERIC;
625
0
    }
626
627
0
    for( int i = 0; ; i++ )
628
0
    {
629
0
        if( sub_read_subtitle_function[i].i_type == p_sys->props.i_type )
630
0
        {
631
0
            msg_Dbg( p_demux, "detected %s format",
632
0
                     sub_read_subtitle_function[i].psz_name );
633
0
            pf_read = sub_read_subtitle_function[i].pf_read;
634
0
            break;
635
0
        }
636
0
    }
637
638
0
    msg_Dbg( p_demux, "loading all subtitles..." );
639
640
0
    if( e_bom == UTF8BOM && /* skip BOM */
641
0
        vlc_stream_Read( p_demux->s, NULL, 3 ) != 3 )
642
0
    {
643
0
        Close( p_this );
644
0
        return VLC_EGENERIC;
645
0
    }
646
647
    /* Load the whole file */
648
0
    text_t txtlines;
649
0
    TextLoad( &txtlines, p_demux->s );
650
651
    /* Parse it */
652
0
    for( size_t i_max = 0; i_max < SIZE_MAX - 500 * sizeof(subtitle_t); )
653
0
    {
654
0
        if( p_sys->subtitles.i_count >= i_max )
655
0
        {
656
0
            i_max += 500;
657
0
            subtitle_t *p_realloc = realloc( p_sys->subtitles.p_array, sizeof(subtitle_t) * i_max );
658
0
            if( p_realloc == NULL )
659
0
            {
660
0
                TextUnload( &txtlines );
661
0
                Close( p_this );
662
0
                return VLC_ENOMEM;
663
0
            }
664
0
            p_sys->subtitles.p_array = p_realloc;
665
0
        }
666
667
0
        if( pf_read( VLC_OBJECT(p_demux), &p_sys->props, &txtlines,
668
0
                     &p_sys->subtitles.p_array[p_sys->subtitles.i_count],
669
0
                     p_sys->subtitles.i_count ) )
670
0
            break;
671
672
0
        p_sys->subtitles.i_count++;
673
0
    }
674
    /* Unload */
675
0
    TextUnload( &txtlines );
676
677
0
    msg_Dbg(p_demux, "loaded %zu subtitles", p_sys->subtitles.i_count );
678
679
    /* *** add subtitle ES *** */
680
0
    if( p_sys->props.i_type == SUB_TYPE_SSA1 ||
681
0
             p_sys->props.i_type == SUB_TYPE_SSA2_4 ||
682
0
             p_sys->props.i_type == SUB_TYPE_ASS )
683
0
    {
684
0
        Fix( p_demux );
685
0
        es_format_Init( &fmt, SPU_ES, VLC_CODEC_SSA );
686
0
    }
687
0
    else if( p_sys->props.i_type == SUB_TYPE_SCC )
688
0
    {
689
0
        es_format_Init( &fmt, SPU_ES, VLC_CODEC_CEA608 );
690
0
        fmt.subs.cc.i_reorder_depth = -1;
691
0
    }
692
0
    else
693
0
        es_format_Init( &fmt, SPU_ES, VLC_CODEC_SUBT );
694
695
0
    p_sys->subtitles.i_current = 0;
696
0
    p_sys->i_length = 0;
697
0
    if( p_sys->subtitles.i_count > 0 )
698
0
        p_sys->i_length = p_sys->subtitles.p_array[p_sys->subtitles.i_count-1].i_stop;
699
700
0
    if( p_sys->props.psz_lang )
701
0
    {
702
0
        fmt.psz_language = p_sys->props.psz_lang;
703
0
        p_sys->props.psz_lang = NULL;
704
0
        msg_Dbg( p_demux, "detected language '%s' of subtitle: %s", fmt.psz_language,
705
0
                 p_demux->psz_location );
706
0
    }
707
0
    else
708
0
    {
709
0
        fmt.psz_language = get_language_from_filename( p_demux->psz_filepath );
710
0
        if( fmt.psz_language )
711
0
            msg_Dbg( p_demux, "selected '%s' as possible filename language substring of subtitle: %s",
712
0
                     fmt.psz_language, p_demux->psz_location );
713
0
    }
714
715
0
    char *psz_description = var_InheritString( p_demux, "sub-description" );
716
0
    if( psz_description && *psz_description )
717
0
        fmt.psz_description = psz_description;
718
0
    else
719
0
        free( psz_description );
720
0
    if( p_sys->props.psz_header != NULL &&
721
0
       (fmt.p_extra = strdup( p_sys->props.psz_header )) )
722
0
    {
723
0
        fmt.i_extra = strlen( p_sys->props.psz_header ) + 1;
724
0
    }
725
726
0
    fmt.i_id = 0;
727
0
    p_sys->es = es_out_Add( p_demux->out, &fmt );
728
0
    es_format_Clean( &fmt );
729
0
    if( p_sys->es == NULL )
730
0
    {
731
0
        Close( p_this );
732
0
        return VLC_EGENERIC;
733
0
    }
734
735
0
    return VLC_SUCCESS;
736
0
}
737
738
/*****************************************************************************
739
 * Close: Close subtitle demux
740
 *****************************************************************************/
741
static void Close( vlc_object_t *p_this )
742
0
{
743
0
    demux_t *p_demux = (demux_t*)p_this;
744
0
    demux_sys_t *p_sys = p_demux->p_sys;
745
746
0
    for( size_t i = 0; i < p_sys->subtitles.i_count; i++ )
747
0
        free( p_sys->subtitles.p_array[i].psz_text );
748
0
    free( p_sys->subtitles.p_array );
749
0
    free( p_sys->props.psz_header );
750
751
0
    free( p_sys );
752
0
}
753
754
static void
755
ResetCurrentIndex( demux_t *p_demux )
756
0
{
757
0
    demux_sys_t *p_sys = p_demux->p_sys;
758
0
    for( size_t i = 0; i < p_sys->subtitles.i_count; i++ )
759
0
    {
760
0
        if( p_sys->subtitles.p_array[i].i_start * p_sys->f_rate >
761
0
            p_sys->i_next_demux_date && i > 0 )
762
0
            break;
763
0
        p_sys->subtitles.i_current = i;
764
0
    }
765
0
}
766
767
/*****************************************************************************
768
 * Control:
769
 *****************************************************************************/
770
static int Control( demux_t *p_demux, int i_query, va_list args )
771
0
{
772
0
    demux_sys_t *p_sys = p_demux->p_sys;
773
0
    double *pf, f;
774
775
0
    switch( i_query )
776
0
    {
777
0
        case DEMUX_CAN_SEEK:
778
0
            *va_arg( args, bool * ) = true;
779
0
            return VLC_SUCCESS;
780
781
0
        case DEMUX_GET_LENGTH:
782
0
            *va_arg( args, vlc_tick_t * ) = p_sys->i_length;
783
0
            return VLC_SUCCESS;
784
785
0
        case DEMUX_GET_TIME:
786
0
            *va_arg( args, vlc_tick_t * ) = p_sys->i_next_demux_date;
787
0
            return VLC_SUCCESS;
788
789
0
        case DEMUX_SET_TIME:
790
0
        {
791
0
            p_sys->b_first_time = true;
792
0
            p_sys->i_next_demux_date = va_arg( args, vlc_tick_t );
793
0
            ResetCurrentIndex( p_demux );
794
0
            return VLC_SUCCESS;
795
0
        }
796
797
0
        case DEMUX_GET_POSITION:
798
0
            pf = va_arg( args, double * );
799
0
            if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
800
0
            {
801
0
                *pf = 1.0;
802
0
            }
803
0
            else if( p_sys->subtitles.i_count > 0 && p_sys->i_length )
804
0
            {
805
0
                *pf = p_sys->i_next_demux_date;
806
0
                *pf /= p_sys->i_length;
807
0
            }
808
0
            else
809
0
            {
810
0
                *pf = 0.0;
811
0
            }
812
0
            return VLC_SUCCESS;
813
814
0
        case DEMUX_SET_POSITION:
815
0
            f = va_arg( args, double );
816
0
            if( p_sys->subtitles.i_count && p_sys->i_length )
817
0
            {
818
0
                vlc_tick_t i64 = VLC_TICK_0 + f * p_sys->i_length;
819
0
                return demux_Control( p_demux, DEMUX_SET_TIME, i64 );
820
0
            }
821
0
            break;
822
823
0
        case DEMUX_CAN_CONTROL_RATE:
824
0
            *va_arg( args, bool * ) = true;
825
0
            return VLC_SUCCESS;
826
0
        case DEMUX_SET_RATE:
827
0
            p_sys->f_rate = *va_arg( args, float * );
828
0
            ResetCurrentIndex( p_demux );
829
0
            return VLC_SUCCESS;
830
0
        case DEMUX_SET_NEXT_DEMUX_TIME:
831
0
            p_sys->b_slave = true;
832
0
            p_sys->i_next_demux_date = va_arg( args, vlc_tick_t ) - VLC_TICK_0;
833
0
            return VLC_SUCCESS;
834
835
0
        case DEMUX_CAN_PAUSE:
836
0
        case DEMUX_SET_PAUSE_STATE:
837
0
        case DEMUX_CAN_CONTROL_PACE:
838
0
            return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args );
839
840
0
        case DEMUX_GET_PTS_DELAY:
841
0
        case DEMUX_GET_FPS:
842
0
        case DEMUX_GET_META:
843
0
        case DEMUX_GET_ATTACHMENTS:
844
0
        case DEMUX_GET_TITLE_INFO:
845
0
        case DEMUX_HAS_UNSUPPORTED_META:
846
0
        case DEMUX_CAN_RECORD:
847
0
        default:
848
0
            break;
849
850
0
    }
851
0
    return VLC_EGENERIC;
852
0
}
853
854
/*****************************************************************************
855
 * Demux: Send subtitle to decoder
856
 *****************************************************************************/
857
static int Demux( demux_t *p_demux )
858
0
{
859
0
    demux_sys_t *p_sys = p_demux->p_sys;
860
861
0
    vlc_tick_t i_barrier = p_sys->i_next_demux_date;
862
863
0
    while( p_sys->subtitles.i_current < p_sys->subtitles.i_count &&
864
0
           ( p_sys->subtitles.p_array[p_sys->subtitles.i_current].i_start *
865
0
             p_sys->f_rate ) <= i_barrier )
866
0
    {
867
0
        const subtitle_t *p_subtitle = &p_sys->subtitles.p_array[p_sys->subtitles.i_current];
868
869
0
        if ( !p_sys->b_slave && p_sys->b_first_time )
870
0
        {
871
0
            es_out_SetPCR( p_demux->out, VLC_TICK_0 + i_barrier );
872
0
            p_sys->b_first_time = false;
873
0
        }
874
875
0
        if( p_subtitle->i_start >= 0 )
876
0
        {
877
0
            block_t *p_block = p_sys->pf_convert( p_subtitle );
878
0
            if( p_block )
879
0
            {
880
0
                p_block->i_dts =
881
0
                p_block->i_pts = VLC_TICK_0 + p_subtitle->i_start * p_sys->f_rate;
882
0
                if( p_subtitle->i_stop != VLC_TICK_INVALID && p_subtitle->i_stop >= p_subtitle->i_start )
883
0
                    p_block->i_length = (p_subtitle->i_stop - p_subtitle->i_start) * p_sys->f_rate;
884
885
0
                es_out_Send( p_demux->out, p_sys->es, p_block );
886
0
            }
887
0
        }
888
889
0
        p_sys->subtitles.i_current++;
890
0
    }
891
892
0
    if ( !p_sys->b_slave )
893
0
    {
894
0
        es_out_SetPCR( p_demux->out, VLC_TICK_0 + i_barrier );
895
0
        p_sys->i_next_demux_date += VLC_TICK_FROM_MS(125);
896
0
    }
897
898
0
    if( p_sys->subtitles.i_current >= p_sys->subtitles.i_count )
899
0
        return VLC_DEMUXER_EOF;
900
901
0
    return VLC_DEMUXER_SUCCESS;
902
0
}
903
904
905
static int subtitle_cmp( const void *first, const void *second )
906
0
{
907
0
    vlc_tick_t result = ((subtitle_t *)(first))->i_start - ((subtitle_t *)(second))->i_start;
908
    /* Return -1, 0 ,1, and not directly subtraction
909
     * as result can be > INT_MAX */
910
0
    return result == 0 ? 0 : result > 0 ? 1 : -1;
911
0
}
912
/*****************************************************************************
913
 * Fix: fix time stamp and order of subtitle
914
 *****************************************************************************/
915
static void Fix( demux_t *p_demux )
916
0
{
917
0
    demux_sys_t *p_sys = p_demux->p_sys;
918
919
    /* *** fix order (to be sure...) *** */
920
0
    qsort( p_sys->subtitles.p_array, p_sys->subtitles.i_count, sizeof( p_sys->subtitles.p_array[0] ), subtitle_cmp);
921
0
}
922
923
static int TextLoad( text_t *txt, stream_t *s )
924
0
{
925
0
    size_t i_line_max;
926
927
    /* init txt */
928
0
    i_line_max          = 500;
929
0
    txt->i_line_count   = 0;
930
0
    txt->i_line         = 0;
931
0
    txt->line           = calloc( i_line_max, sizeof( char * ) );
932
0
    if( !txt->line )
933
0
        return VLC_ENOMEM;
934
935
    /* load the complete file */
936
0
    for( ;; )
937
0
    {
938
0
        char *psz = vlc_stream_ReadLine( s );
939
940
0
        if( psz == NULL )
941
0
            break;
942
943
0
        txt->line[txt->i_line_count] = psz;
944
0
        if( txt->i_line_count + 1 >= i_line_max )
945
0
        {
946
0
            i_line_max += 100;
947
0
            char **p_realloc = realloc( txt->line, i_line_max * sizeof( char * ) );
948
0
            if( p_realloc == NULL )
949
0
                return VLC_ENOMEM;
950
0
            txt->line = p_realloc;
951
0
        }
952
0
        txt->i_line_count++;
953
0
    }
954
955
0
    if( txt->i_line_count == 0 )
956
0
    {
957
0
        free( txt->line );
958
0
        return VLC_EGENERIC;
959
0
    }
960
961
0
    return VLC_SUCCESS;
962
0
}
963
static void TextUnload( text_t *txt )
964
0
{
965
0
    if( txt->i_line_count )
966
0
    {
967
0
        for( size_t i = 0; i < txt->i_line_count; i++ )
968
0
            free( txt->line[i] );
969
0
        free( txt->line );
970
0
    }
971
0
    txt->i_line       = 0;
972
0
    txt->i_line_count = 0;
973
0
}
974
975
static char *TextGetLine( text_t *txt )
976
0
{
977
0
    if( txt->i_line >= txt->i_line_count )
978
0
        return( NULL );
979
980
0
    return txt->line[txt->i_line++];
981
0
}
982
static void TextPreviousLine( text_t *txt )
983
0
{
984
0
    if( txt->i_line > 0 )
985
0
        txt->i_line--;
986
0
}
987
988
/*****************************************************************************
989
 * Specific Subtitle function
990
 *****************************************************************************/
991
/* ParseMicroDvd:
992
 *  Format:
993
 *      {n1}{n2}Line1|Line2|Line3....
994
 *  where n1 and n2 are the video frame number (n2 can be empty)
995
 */
996
static int ParseMicroDvd( vlc_object_t *p_obj, subs_properties_t *p_props,
997
                          text_t *txt, subtitle_t *p_subtitle,
998
                          size_t i_idx )
999
0
{
1000
0
    VLC_UNUSED( i_idx );
1001
0
    char *psz_text;
1002
0
    int  i_start;
1003
0
    int  i_stop;
1004
0
    int  i;
1005
1006
0
    for( ;; )
1007
0
    {
1008
0
        const char *s = TextGetLine( txt );
1009
0
        if( !s )
1010
0
            return VLC_EGENERIC;
1011
1012
0
        psz_text = malloc( strlen(s) + 1 );
1013
0
        if( !psz_text )
1014
0
            return VLC_ENOMEM;
1015
1016
0
        i_start = 0;
1017
0
        i_stop  = -1;
1018
0
        if( sscanf( s, "{%d}{}%[^\r\n]", &i_start, psz_text ) == 2 ||
1019
0
            sscanf( s, "{%d}{%d}%[^\r\n]", &i_start, &i_stop, psz_text ) == 3)
1020
0
        {
1021
0
            if( i_start != 1 || i_stop != 1 )
1022
0
                break;
1023
1024
            /* We found a possible setting of the framerate "{1}{1}23.976" */
1025
            /* Check if it's usable, and if the sub-original-fps is not set */
1026
0
            float f_fps = vlc_strtof_c( psz_text, NULL );
1027
0
            if( f_fps > 0.f && var_GetFloat( p_obj, "sub-original-fps" ) <= 0.f )
1028
0
                p_props->i_microsecperframe = llroundf((float)CLOCK_FREQ / f_fps);
1029
0
        }
1030
0
        free( psz_text );
1031
0
    }
1032
1033
    /* replace | by \n */
1034
0
    for( i = 0; psz_text[i] != '\0'; i++ )
1035
0
    {
1036
0
        if( psz_text[i] == '|' )
1037
0
            psz_text[i] = '\n';
1038
0
    }
1039
1040
    /* */
1041
0
    p_subtitle->i_start  =  VLC_TICK_0 + i_start * p_props->i_microsecperframe;
1042
0
    p_subtitle->i_stop   = i_stop >= 0 ? (VLC_TICK_0 + i_stop  * p_props->i_microsecperframe) : VLC_TICK_INVALID;
1043
0
    p_subtitle->psz_text = psz_text;
1044
0
    return VLC_SUCCESS;
1045
0
}
1046
1047
/* ParseSubRipSubViewer
1048
 *  Format SubRip
1049
 *      n
1050
 *      h1:m1:s1,d1 --> h2:m2:s2,d2
1051
 *      Line1
1052
 *      Line2
1053
 *      ....
1054
 *      [Empty line]
1055
 *  Format SubViewer v1/v2
1056
 *      h1:m1:s1.d1,h2:m2:s2.d2
1057
 *      Line1[br]Line2
1058
 *      Line3
1059
 *      ...
1060
 *      [empty line]
1061
 *  We ignore line number for SubRip
1062
 */
1063
static int ParseSubRipSubViewer( vlc_object_t *p_obj, subs_properties_t *p_props,
1064
                                 text_t *txt, subtitle_t *p_subtitle,
1065
                                 int (* pf_parse_timing)(subtitle_t *, const char *),
1066
                                 bool b_replace_br )
1067
0
{
1068
0
    VLC_UNUSED(p_obj);
1069
0
    VLC_UNUSED(p_props);
1070
0
    char    *psz_text;
1071
1072
0
    for( ;; )
1073
0
    {
1074
0
        const char *s = TextGetLine( txt );
1075
1076
0
        if( !s )
1077
0
            return VLC_EGENERIC;
1078
1079
0
        if( pf_parse_timing( p_subtitle, s) == VLC_SUCCESS &&
1080
0
            p_subtitle->i_start < p_subtitle->i_stop )
1081
0
        {
1082
0
            break;
1083
0
        }
1084
0
    }
1085
1086
    /* Now read text until an empty line */
1087
0
    size_t i_old = 0;
1088
0
    psz_text = NULL;
1089
0
    for( ;; )
1090
0
    {
1091
0
        const char *s = TextGetLine( txt );
1092
0
        size_t i_len;
1093
1094
0
        i_len = s ? strlen( s ) : 0;
1095
0
        if( i_len <= 0 )
1096
0
        {
1097
0
            if (psz_text)
1098
0
                psz_text[i_old] = '\0';
1099
0
            p_subtitle->psz_text = psz_text;
1100
0
            return VLC_SUCCESS;
1101
0
        }
1102
1103
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
1104
0
        if( !psz_text )
1105
0
            return VLC_ENOMEM;
1106
1107
0
        memcpy( &psz_text[i_old], s, i_len );
1108
0
        psz_text[i_old + i_len + 0] = '\n';
1109
0
        i_old += i_len + 1;
1110
1111
        /* replace [br] by \n */
1112
0
        if( b_replace_br )
1113
0
        {
1114
0
            char *p;
1115
1116
0
            while( ( p = strstr( psz_text, "[br]" ) ) )
1117
0
            {
1118
0
                *p++ = '\n';
1119
0
                memmove( p, &p[3], strlen(&p[3])+1 );
1120
0
                i_old -= 3;
1121
0
            }
1122
0
        }
1123
0
    }
1124
0
}
1125
1126
/* subtitle_ParseSubRipTimingValue
1127
 * Parses SubRip timing value.
1128
 */
1129
static int subtitle_ParseSubRipTimingValue(vlc_tick_t *timing_value,
1130
                                           const char *s, size_t length)
1131
0
{
1132
0
    int h1, m1, s1, d1 = 0;
1133
0
    int64_t sec, ms, total;
1134
1135
0
    int count;
1136
0
    if (sscanf(s, "%d:%d:%d,%d%n", &h1, &m1, &s1, &d1, &count) == 4
1137
0
        && (size_t)count <= length)
1138
0
        goto success;
1139
1140
0
    if (sscanf(s, "%d:%d:%d.%d%n", &h1, &m1, &s1, &d1, &count) == 4
1141
0
        && (size_t)count <= length)
1142
0
        goto success;
1143
1144
0
    d1 = 0;
1145
0
    if (sscanf(s, "%d:%d:%d%n", &h1, &m1, &s1, &count) == 3
1146
0
        && (size_t)count <= length)
1147
0
        goto success;
1148
1149
0
    return VLC_EGENERIC;
1150
1151
0
success:
1152
0
    if (ckd_mul(&sec, h1, 3600) ||
1153
0
        ckd_mul(&ms,  m1, 60) ||
1154
0
        ckd_add(&total, sec, ms) ||
1155
0
        ckd_add(&total, total, s1))
1156
0
        return VLC_EINVAL;
1157
1158
0
    (*timing_value) = VLC_TICK_0
1159
0
        + vlc_tick_from_sec(total)
1160
0
        + VLC_TICK_FROM_MS(d1);
1161
1162
0
    return VLC_SUCCESS;
1163
1164
0
}
1165
1166
/* subtitle_ParseSubRipTiming
1167
 * Parses SubRip timing.
1168
 */
1169
static int subtitle_ParseSubRipTiming( subtitle_t *p_subtitle,
1170
                                       const char *s )
1171
0
{
1172
0
    const char *delimiter = strstr(s, " --> ");
1173
0
    if (delimiter == NULL || delimiter == s)
1174
0
        return VLC_EGENERIC;
1175
1176
0
    int ret = subtitle_ParseSubRipTimingValue(&p_subtitle->i_start, s, (size_t)(delimiter - s));
1177
0
    if (ret != VLC_SUCCESS)
1178
0
        return ret;
1179
1180
0
    const char *right = delimiter + strlen(" --> ");
1181
0
    return subtitle_ParseSubRipTimingValue(&p_subtitle->i_stop, right, strlen(right));
1182
0
}
1183
1184
/* ParseSubRip
1185
 */
1186
static int  ParseSubRip( vlc_object_t *p_obj, subs_properties_t *p_props,
1187
                         text_t *txt, subtitle_t *p_subtitle,
1188
                         size_t i_idx )
1189
0
{
1190
0
    VLC_UNUSED( i_idx );
1191
0
    return ParseSubRipSubViewer( p_obj, p_props, txt, p_subtitle,
1192
0
                                 &subtitle_ParseSubRipTiming,
1193
0
                                 false );
1194
0
}
1195
1196
/* subtitle_ParseSubViewerTiming
1197
 * Parses SubViewer timing.
1198
 */
1199
static int subtitle_ParseSubViewerTiming( subtitle_t *p_subtitle,
1200
                                   const char *s )
1201
0
{
1202
0
    int h1, m1, s1, d1, h2, m2, s2, d2;
1203
1204
0
    if( sscanf( s, "%d:%d:%d.%d,%d:%d:%d.%d",
1205
0
                &h1, &m1, &s1, &d1, &h2, &m2, &s2, &d2) != 8 )
1206
0
        return VLC_EGENERIC;
1207
1208
0
    int64_t sec, ms, total;
1209
0
    if (ckd_mul(&sec, h1, 3600) ||
1210
0
        ckd_mul(&ms,  m1, 60) ||
1211
0
        ckd_add(&total, sec, ms) ||
1212
0
        ckd_add(&total, total, s1))
1213
0
        return VLC_EINVAL;
1214
1215
0
    p_subtitle->i_start = vlc_tick_from_sec( total ) +
1216
0
                          VLC_TICK_FROM_MS( d1 ) + VLC_TICK_0;
1217
1218
0
    if (ckd_mul(&sec, h2, 3600) ||
1219
0
        ckd_mul(&ms,  m2, 60) ||
1220
0
        ckd_add(&total, sec, ms) ||
1221
0
        ckd_add(&total, total, s2))
1222
0
        return VLC_EINVAL;
1223
1224
0
    p_subtitle->i_stop  = vlc_tick_from_sec( total ) +
1225
0
                          VLC_TICK_FROM_MS( d2 ) + VLC_TICK_0;
1226
0
    return VLC_SUCCESS;
1227
0
}
1228
1229
/* ParseSubViewer
1230
 */
1231
static int  ParseSubViewer( vlc_object_t *p_obj, subs_properties_t *p_props,
1232
                            text_t *txt, subtitle_t *p_subtitle,
1233
                            size_t i_idx )
1234
0
{
1235
0
    VLC_UNUSED( i_idx );
1236
1237
0
    return ParseSubRipSubViewer( p_obj, p_props, txt, p_subtitle,
1238
0
                                 &subtitle_ParseSubViewerTiming,
1239
0
                                 true );
1240
0
}
1241
1242
/* ParseSSA
1243
 */
1244
static int  ParseSSA( vlc_object_t *p_obj, subs_properties_t *p_props,
1245
                      text_t *txt, subtitle_t *p_subtitle,
1246
                      size_t i_idx )
1247
0
{
1248
0
    VLC_UNUSED(p_obj);
1249
0
    size_t header_len = 0;
1250
1251
0
    for( ;; )
1252
0
    {
1253
0
        const char *s = TextGetLine( txt );
1254
0
        int h1, m1, s1, c1, h2, m2, s2, c2;
1255
0
        char *psz_text, *psz_temp;
1256
0
        char temp[16];
1257
1258
0
        if( !s )
1259
0
            return VLC_EGENERIC;
1260
1261
        /* We expect (SSA2-4):
1262
         * Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1263
         * Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1264
         *
1265
         * SSA-1 is similar but only has 8 commas up until the subtitle text. Probably the Effect field is no present, but not 100 % sure.
1266
         */
1267
1268
        /* For ASS:
1269
         * Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1270
         * Dialogue: Layer#,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1271
         */
1272
1273
0
        psz_text = NULL;
1274
0
        if( s[0] == 'D' || s[0] == 'L' )
1275
0
        {
1276
            /* The output text is always shorter than the input text. */
1277
0
            psz_text = malloc( strlen(s) );
1278
0
            if( !psz_text )
1279
0
                return VLC_ENOMEM;
1280
0
        }
1281
1282
        /* Try to capture the language property */
1283
0
        if( s[0] == 'L' &&
1284
0
            sscanf( s, "Language: %[^\r\n]", psz_text ) == 1 )
1285
0
        {
1286
0
            free( p_props->psz_lang ); /* just in case of multiple instances */
1287
0
            p_props->psz_lang = psz_text;
1288
0
            psz_text = NULL;
1289
0
        }
1290
0
        else if( s[0] == 'D' &&
1291
0
            sscanf( s,
1292
0
                    "Dialogue: %15[^,],%d:%d:%d.%d,%d:%d:%d.%d,%[^\r\n]",
1293
0
                    temp,
1294
0
                    &h1, &m1, &s1, &c1,
1295
0
                    &h2, &m2, &s2, &c2,
1296
0
                    psz_text ) == 10 )
1297
0
        {
1298
            /* The dec expects: ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text */
1299
            /* (Layer comes from ASS specs ... it's empty for SSA.) */
1300
0
            if( p_props->i_type == SUB_TYPE_SSA1 )
1301
0
            {
1302
                /* SSA1 has only 8 commas before the text starts, not 9 */
1303
0
                memmove( &psz_text[1], psz_text, strlen(psz_text)+1 );
1304
0
                psz_text[0] = ',';
1305
0
            }
1306
0
            else
1307
0
            {
1308
0
                int i_layer = ( p_props->i_type == SUB_TYPE_ASS ) ? atoi( temp ) : 0;
1309
1310
                /* ReadOrder, Layer, %s(rest of fields) */
1311
0
                if( asprintf( &psz_temp, "%zu,%d,%s", i_idx, i_layer, psz_text ) == -1 )
1312
0
                {
1313
0
                    free( psz_text );
1314
0
                    return VLC_ENOMEM;
1315
0
                }
1316
1317
0
                free( psz_text );
1318
0
                psz_text = psz_temp;
1319
0
            }
1320
1321
0
            p_subtitle->i_start = vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 ) +
1322
0
                                  VLC_TICK_FROM_MS( c1 * 10 ) + VLC_TICK_0;
1323
0
            p_subtitle->i_stop  = vlc_tick_from_sec( h2 * 3600 + m2 * 60 + s2 ) +
1324
0
                                  VLC_TICK_FROM_MS( c2 * 10 ) + VLC_TICK_0;
1325
0
            p_subtitle->psz_text = psz_text;
1326
0
            return VLC_SUCCESS;
1327
0
        }
1328
0
        free( psz_text );
1329
1330
        /* All the other stuff we add to the header field */
1331
0
        if( header_len == 0 && p_props->psz_header )
1332
0
            header_len = strlen( p_props->psz_header );
1333
1334
0
        size_t s_len = strlen( s );
1335
0
        p_props->psz_header = realloc_or_free( p_props->psz_header, header_len + s_len + 2 );
1336
0
        if( !p_props->psz_header )
1337
0
            return VLC_ENOMEM;
1338
0
        snprintf( p_props->psz_header + header_len, s_len + 2, "%s\n", s );
1339
0
        header_len += s_len + 1;
1340
0
    }
1341
0
}
1342
1343
/* ParseVplayer
1344
 *  Format
1345
 *      h:m:s:Line1|Line2|Line3....
1346
 *  or
1347
 *      h:m:s Line1|Line2|Line3....
1348
 */
1349
static int ParseVplayer( vlc_object_t *p_obj, subs_properties_t *p_props,
1350
                         text_t *txt, subtitle_t *p_subtitle,
1351
                         size_t i_idx )
1352
0
{
1353
0
    VLC_UNUSED(p_obj);
1354
0
    VLC_UNUSED(p_props);
1355
0
    VLC_UNUSED( i_idx );
1356
0
    char *psz_text;
1357
1358
0
    for( ;; )
1359
0
    {
1360
0
        const char *s = TextGetLine( txt );
1361
0
        int h1, m1, s1;
1362
1363
0
        if( !s )
1364
0
            return VLC_EGENERIC;
1365
1366
0
        psz_text = malloc( strlen( s ) + 1 );
1367
0
        if( !psz_text )
1368
0
            return VLC_ENOMEM;
1369
1370
0
        if( sscanf( s, "%d:%d:%d%*c%[^\r\n]",
1371
0
                    &h1, &m1, &s1, psz_text ) == 4 )
1372
0
        {
1373
0
            p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 );
1374
0
            p_subtitle->i_stop  = -1;
1375
0
            break;
1376
0
        }
1377
0
        free( psz_text );
1378
0
    }
1379
1380
    /* replace | by \n */
1381
0
    for( size_t i = 0; psz_text[i] != '\0'; i++ )
1382
0
    {
1383
0
        if( psz_text[i] == '|' )
1384
0
            psz_text[i] = '\n';
1385
0
    }
1386
0
    p_subtitle->psz_text = psz_text;
1387
0
    return VLC_SUCCESS;
1388
0
}
1389
1390
/* ParseSami
1391
 */
1392
static const char *ParseSamiSearch( text_t *txt,
1393
                                    const char *psz_start, const char *psz_str )
1394
0
{
1395
0
    if( psz_start && strcasestr( psz_start, psz_str ) )
1396
0
    {
1397
0
        const char *s = strcasestr( psz_start, psz_str );
1398
0
        return &s[strlen( psz_str )];
1399
0
    }
1400
1401
0
    for( ;; )
1402
0
    {
1403
0
        const char *p = TextGetLine( txt );
1404
0
        if( !p )
1405
0
            return NULL;
1406
1407
0
        const char *s = strcasestr( p, psz_str );
1408
0
        if( s != NULL )
1409
0
            return &s[strlen( psz_str )];
1410
0
    }
1411
0
}
1412
static int ParseSami( vlc_object_t *p_obj, subs_properties_t *p_props,
1413
                      text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1414
0
{
1415
0
    VLC_UNUSED(p_obj);
1416
0
    VLC_UNUSED(p_props);
1417
0
    VLC_UNUSED( i_idx );
1418
0
    const char *s;
1419
0
    int64_t i_start;
1420
1421
0
    unsigned int i_text;
1422
0
    char text[8192]; /* Arbitrary but should be long enough */
1423
1424
    /* search "Start=" */
1425
0
    s = ParseSamiSearch( txt, p_props->sami.psz_start, "Start=" );
1426
0
    p_props->sami.psz_start = NULL;
1427
0
    if( !s )
1428
0
        return VLC_EGENERIC;
1429
1430
    /* get start value */
1431
0
    char *psz_end;
1432
0
    i_start = strtol( s, &psz_end, 0 );
1433
0
    s = psz_end;
1434
1435
    /* search <P */
1436
0
    if( !( s = ParseSamiSearch( txt, s, "<P" ) ) )
1437
0
        return VLC_EGENERIC;
1438
1439
    /* search > */
1440
0
    if( !( s = ParseSamiSearch( txt, s, ">" ) ) )
1441
0
        return VLC_EGENERIC;
1442
1443
0
    i_text = 0;
1444
0
    text[0] = '\0';
1445
    /* now get all txt until  a "Start=" line */
1446
0
    for( ;; )
1447
0
    {
1448
0
        char c = '\0';
1449
        /* Search non empty line */
1450
0
        while( s && *s == '\0' )
1451
0
            s = TextGetLine( txt );
1452
0
        if( !s )
1453
0
            break;
1454
1455
0
        if( *s == '<' )
1456
0
        {
1457
0
            if( !strncasecmp( s, "<br", 3 ) )
1458
0
            {
1459
0
                c = '\n';
1460
0
            }
1461
0
            else if( strcasestr( s, "Start=" ) )
1462
0
            {
1463
0
                p_props->sami.psz_start = s;
1464
0
                break;
1465
0
            }
1466
0
            s = ParseSamiSearch( txt, s, ">" );
1467
0
        }
1468
0
        else if( !strncmp( s, "&nbsp;", 6 ) )
1469
0
        {
1470
0
            c = ' ';
1471
0
            s += 6;
1472
0
        }
1473
0
        else if( *s == '\t' )
1474
0
        {
1475
0
            c = ' ';
1476
0
            s++;
1477
0
        }
1478
0
        else
1479
0
        {
1480
0
            c = *s;
1481
0
            s++;
1482
0
        }
1483
0
        if( c != '\0' && i_text+1 < sizeof(text) )
1484
0
        {
1485
0
            text[i_text++] = c;
1486
0
            text[i_text] = '\0';
1487
0
        }
1488
0
    }
1489
1490
0
    p_subtitle->i_start = VLC_TICK_0 + VLC_TICK_FROM_MS(i_start);
1491
0
    p_subtitle->i_stop  = -1;
1492
0
    p_subtitle->psz_text = strdup( text );
1493
1494
0
    return VLC_SUCCESS;
1495
0
}
1496
1497
/* ParseDVDSubtitle
1498
 *  Format
1499
 *      {T h1:m1:s1:c1
1500
 *      Line1
1501
 *      Line2
1502
 *      ...
1503
 *      }
1504
 * TODO it can have a header
1505
 *      { HEAD
1506
 *          ...
1507
 *          CODEPAGE=...
1508
 *          FORMAT=...
1509
 *          LANG=English
1510
 *      }
1511
 *      LANG support would be cool
1512
 *      CODEPAGE is probably mandatory FIXME
1513
 */
1514
static int ParseDVDSubtitle(vlc_object_t *p_obj, subs_properties_t *p_props,
1515
                            text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1516
0
{
1517
0
    VLC_UNUSED(p_obj);
1518
0
    VLC_UNUSED(p_props);
1519
0
    VLC_UNUSED( i_idx );
1520
0
    char *psz_text;
1521
1522
0
    for( ;; )
1523
0
    {
1524
0
        const char *s = TextGetLine( txt );
1525
0
        int h1, m1, s1, c1;
1526
1527
0
        if( !s )
1528
0
            return VLC_EGENERIC;
1529
1530
0
        if( sscanf( s,
1531
0
                    "{T %d:%d:%d:%d",
1532
0
                    &h1, &m1, &s1, &c1 ) == 4 )
1533
0
        {
1534
0
            p_subtitle->i_start = vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 ) +
1535
0
                                  VLC_TICK_FROM_MS( c1 * 10 ) + VLC_TICK_0;
1536
0
            p_subtitle->i_stop = -1;
1537
0
            break;
1538
0
        }
1539
0
    }
1540
1541
    /* Now read text until a line containing "}" */
1542
0
    size_t i_old = 0;
1543
0
    psz_text = NULL;
1544
0
    for( ;; )
1545
0
    {
1546
0
        const char *s = TextGetLine( txt );
1547
0
        size_t i_len;
1548
1549
0
        if( !s )
1550
0
        {
1551
0
            free( psz_text );
1552
0
            return VLC_EGENERIC;
1553
0
        }
1554
1555
0
        i_len = strlen( s );
1556
0
        if( i_len == 1 && s[0] == '}')
1557
0
        {
1558
0
            if (psz_text)
1559
0
                psz_text[i_old] = '\0';
1560
0
            p_subtitle->psz_text = psz_text;
1561
0
            return VLC_SUCCESS;
1562
0
        }
1563
1564
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
1565
0
        if( !psz_text )
1566
0
            return VLC_ENOMEM;
1567
1568
0
        memcpy( &psz_text[i_old], s, i_len );
1569
0
        psz_text[i_old + i_len + 0] = '\n';
1570
0
        i_old += i_len + 1;
1571
0
    }
1572
0
}
1573
1574
/* ParseMPL2
1575
 *  Format
1576
 *     [n1][n2]Line1|Line2|Line3...
1577
 *  where n1 and n2 are the video frame number (n2 can be empty)
1578
 */
1579
static int ParseMPL2(vlc_object_t *p_obj, subs_properties_t *p_props,
1580
                     text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1581
0
{
1582
0
    VLC_UNUSED(p_obj);
1583
0
    VLC_UNUSED(p_props);
1584
0
    VLC_UNUSED( i_idx );
1585
0
    char *psz_text;
1586
0
    int i;
1587
1588
0
    for( ;; )
1589
0
    {
1590
0
        const char *s = TextGetLine( txt );
1591
0
        int i_start;
1592
0
        int i_stop;
1593
1594
0
        if( !s )
1595
0
            return VLC_EGENERIC;
1596
1597
0
        psz_text = malloc( strlen(s) + 1 );
1598
0
        if( !psz_text )
1599
0
            return VLC_ENOMEM;
1600
1601
0
        i_start = 0;
1602
0
        i_stop  = -1;
1603
0
        if( sscanf( s, "[%d][] %[^\r\n]", &i_start, psz_text ) == 2 ||
1604
0
            sscanf( s, "[%d][%d] %[^\r\n]", &i_start, &i_stop, psz_text ) == 3)
1605
0
        {
1606
0
            p_subtitle->i_start = VLC_TICK_0 + VLC_TICK_FROM_MS(i_start * 100);
1607
0
            p_subtitle->i_stop  = i_stop >= 0 ? VLC_TICK_0 + VLC_TICK_FROM_MS(i_stop  * 100) : VLC_TICK_INVALID;
1608
0
            break;
1609
0
        }
1610
0
        free( psz_text );
1611
0
    }
1612
1613
0
    for( i = 0; psz_text[i] != '\0'; )
1614
0
    {
1615
        /* replace | by \n */
1616
0
        if( psz_text[i] == '|' )
1617
0
            psz_text[i] = '\n';
1618
1619
        /* Remove italic */
1620
0
        if( psz_text[i] == '/' && ( i == 0 || psz_text[i-1] == '\n' ) )
1621
0
            memmove( &psz_text[i], &psz_text[i+1], strlen(&psz_text[i+1])+1 );
1622
0
        else
1623
0
            i++;
1624
0
    }
1625
0
    p_subtitle->psz_text = psz_text;
1626
0
    return VLC_SUCCESS;
1627
0
}
1628
1629
static int ParseAQT(vlc_object_t *p_obj, subs_properties_t *p_props, text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1630
0
{
1631
0
    VLC_UNUSED(p_obj);
1632
0
    VLC_UNUSED(p_props);
1633
0
    VLC_UNUSED( i_idx );
1634
1635
0
    char *psz_text = NULL;
1636
0
    size_t i_old = 0;
1637
0
    size_t i_len;
1638
0
    int i_firstline = 1;
1639
1640
0
    for( ;; )
1641
0
    {
1642
0
        int t; /* Time */
1643
1644
0
        const char *s = TextGetLine( txt );
1645
1646
0
        if( !s )
1647
0
        {
1648
0
            free( psz_text );
1649
0
            return VLC_EGENERIC;
1650
0
        }
1651
1652
        /* Data Lines */
1653
0
        if( sscanf (s, "-->> %d", &t) == 1)
1654
0
        {
1655
            /* Starting of a subtitle */
1656
0
            if( i_firstline )
1657
0
            {
1658
0
                p_subtitle->i_start = VLC_TICK_0 + t * p_props->i_microsecperframe;
1659
0
                i_firstline = 0;
1660
0
            }
1661
            /* We have been too far: end of the subtitle, begin of next */
1662
0
            else
1663
0
            {
1664
0
                p_subtitle->i_stop  = VLC_TICK_0 + t * p_props->i_microsecperframe;
1665
0
                break;
1666
0
            }
1667
0
        }
1668
        /* Text Lines */
1669
0
        else
1670
0
        {
1671
0
            i_len = strlen( s );
1672
0
            psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
1673
0
            if( !psz_text )
1674
0
                 return VLC_ENOMEM;
1675
1676
0
            memcpy( &psz_text[i_old], s, i_len );
1677
0
            psz_text[i_old + i_len + 0] = '\n';
1678
0
            i_old += i_len + 1;
1679
0
            if( txt->i_line == txt->i_line_count )
1680
0
                break;
1681
0
        }
1682
0
    }
1683
0
    if (psz_text)
1684
0
        psz_text[i_old] = '\0';
1685
0
    p_subtitle->psz_text = psz_text;
1686
0
    return VLC_SUCCESS;
1687
0
}
1688
1689
static int ParsePJS(vlc_object_t *p_obj, subs_properties_t *p_props,
1690
                    text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1691
0
{
1692
0
    VLC_UNUSED(p_obj);
1693
0
    VLC_UNUSED(p_props);
1694
0
    VLC_UNUSED( i_idx );
1695
1696
0
    char *psz_text;
1697
0
    int i;
1698
1699
0
    for( ;; )
1700
0
    {
1701
0
        const char *s = TextGetLine( txt );
1702
0
        int t1, t2;
1703
1704
0
        if( !s )
1705
0
            return VLC_EGENERIC;
1706
1707
0
        psz_text = malloc( strlen(s) + 1 );
1708
0
        if( !psz_text )
1709
0
            return VLC_ENOMEM;
1710
1711
        /* Data Lines */
1712
0
        if( sscanf (s, "%d,%d,\"%[^\n\r]", &t1, &t2, psz_text ) == 3 )
1713
0
        {
1714
            /* 1/10th of second ? Frame based ? FIXME */
1715
0
            p_subtitle->i_start = VLC_TICK_0 + 10 * t1;
1716
0
            p_subtitle->i_stop = VLC_TICK_0 + 10 * t2;
1717
            /* Remove latest " */
1718
0
            psz_text[ strlen(psz_text) - 1 ] = '\0';
1719
1720
0
            break;
1721
0
        }
1722
0
        free( psz_text );
1723
0
    }
1724
1725
    /* replace | by \n */
1726
0
    for( i = 0; psz_text[i] != '\0'; i++ )
1727
0
    {
1728
0
        if( psz_text[i] == '|' )
1729
0
            psz_text[i] = '\n';
1730
0
    }
1731
1732
0
    p_subtitle->psz_text = psz_text;
1733
0
    msg_Dbg( p_obj, "%s", psz_text );
1734
0
    return VLC_SUCCESS;
1735
0
}
1736
1737
static int ParseMPSub( vlc_object_t *p_obj, subs_properties_t *p_props,
1738
                       text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1739
0
{
1740
0
    VLC_UNUSED( i_idx );
1741
1742
0
    if( !p_props->mpsub.b_inited )
1743
0
    {
1744
0
        p_props->mpsub.f_total = 0.0;
1745
0
        p_props->mpsub.i_factor = 0;
1746
1747
0
        p_props->mpsub.b_inited = true;
1748
0
    }
1749
1750
0
    for( ;; )
1751
0
    {
1752
0
        const char *s = TextGetLine( txt );
1753
0
        if( !s )
1754
0
        {
1755
0
            return VLC_EGENERIC;
1756
0
        }
1757
1758
0
        if ( *s =='#' || *s == '\0' )
1759
0
            continue;
1760
1761
        /* Data Lines */
1762
0
        float wait, duration;
1763
0
        if( sscanf( s, "%f %f", &wait, &duration ) == 2 )
1764
0
        {
1765
0
            float f1 = wait;
1766
0
            float f2 = duration;
1767
0
            p_props->mpsub.f_total += f1 * p_props->mpsub.i_factor;
1768
0
            p_subtitle->i_start = VLC_TICK_0 + llroundf(10000.f * p_props->mpsub.f_total);
1769
0
            p_props->mpsub.f_total += f2 * p_props->mpsub.i_factor;
1770
0
            p_subtitle->i_stop = VLC_TICK_0 + llroundf(10000.f * p_props->mpsub.f_total);
1771
0
            break;
1772
0
        }
1773
1774
0
        if( !strncmp( s, "FORMAT=", strlen("FORMAT=") ) )
1775
0
        {
1776
0
            const char *psz_format = s + strlen( "FORMAT=" );
1777
0
            if( !strncmp( psz_format, "TIME", strlen("TIME") ) && (psz_format[4] == '\0' || psz_format[4] == ' ') )
1778
0
            {
1779
                // FORMAT=TIME may be followed by a comment
1780
0
                p_props->mpsub.i_factor = 100;
1781
0
            }
1782
0
            else
1783
0
            {
1784
0
                float f_fps;
1785
0
                if( sscanf( psz_format, "%f", &f_fps ) == 1 )
1786
0
                {
1787
0
                    if( f_fps > 0.f && var_GetFloat( p_obj, "sub-original-fps" ) <= 0.f )
1788
0
                        var_SetFloat( p_obj, "sub-original-fps", f_fps );
1789
1790
0
                    p_props->mpsub.i_factor = 1;
1791
0
                }
1792
0
            }
1793
0
        }
1794
0
    }
1795
1796
0
    char *psz_text = NULL;
1797
0
    size_t i_old = 0;
1798
0
    for( ;; )
1799
0
    {
1800
0
        const char *s = TextGetLine( txt );
1801
1802
0
        if( !s )
1803
0
        {
1804
0
            free( psz_text );
1805
0
            return VLC_EGENERIC;
1806
0
        }
1807
1808
0
        size_t i_len = strlen( s );
1809
0
        if( i_len == 0 )
1810
0
            break;
1811
1812
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
1813
0
        if( !psz_text )
1814
0
             return VLC_ENOMEM;
1815
1816
0
        memcpy( &psz_text[i_old], s, i_len );
1817
0
        psz_text[i_old + i_len + 0] = '\n';
1818
0
        i_old += i_len + 1;
1819
0
    }
1820
1821
0
    if (psz_text)
1822
0
        psz_text[i_old] = '\0';
1823
0
    p_subtitle->psz_text = psz_text;
1824
0
    return VLC_SUCCESS;
1825
0
}
1826
1827
static int ParseJSS( vlc_object_t *p_obj, subs_properties_t *p_props,
1828
                     text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
1829
0
{
1830
0
    VLC_UNUSED( i_idx );
1831
0
    char         *psz_text, *psz_orig;
1832
0
    char         *psz_text2, *psz_orig2;
1833
1834
0
    if( !p_props->jss.b_inited )
1835
0
    {
1836
0
        p_props->jss.i_comment = 0;
1837
0
        p_props->jss.i_time_resolution = 30;
1838
0
        p_props->jss.i_time_shift = 0;
1839
1840
0
        p_props->jss.b_inited = true;
1841
0
    }
1842
1843
    /* Parse the main lines */
1844
0
    for( ;; )
1845
0
    {
1846
0
        const char *s = TextGetLine( txt );
1847
0
        if( !s )
1848
0
            return VLC_EGENERIC;
1849
1850
0
        size_t line_length = strlen( s );
1851
0
        psz_orig = malloc( line_length + 1 );
1852
0
        if( !psz_orig )
1853
0
            return VLC_ENOMEM;
1854
0
        psz_text = psz_orig;
1855
1856
        /* Complete time lines */
1857
0
        int h1, h2, m1, m2, s1, s2, f1, f2;
1858
0
        if( sscanf( s, "%d:%d:%d.%d %d:%d:%d.%d %[^\n\r]",
1859
0
                    &h1, &m1, &s1, &f1, &h2, &m2, &s2, &f2, psz_text ) == 9 )
1860
0
        {
1861
0
            p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec( ( h1 *3600 + m1 * 60 + s1 ) +
1862
0
                (int64_t)( ( f1 +  p_props->jss.i_time_shift ) / p_props->jss.i_time_resolution ) );
1863
0
            p_subtitle->i_stop = VLC_TICK_0 + vlc_tick_from_sec( ( h2 *3600 + m2 * 60 + s2 ) +
1864
0
                (int64_t)( ( f2 +  p_props->jss.i_time_shift ) / p_props->jss.i_time_resolution ) );
1865
0
            break;
1866
0
        }
1867
        /* Short time lines */
1868
0
        else if( sscanf( s, "@%d @%d %[^\n\r]", &f1, &f2, psz_text ) == 3 )
1869
0
        {
1870
0
            p_subtitle->i_start = VLC_TICK_0 +
1871
0
                    vlc_tick_from_sec( (f1 + p_props->jss.i_time_shift ) / p_props->jss.i_time_resolution );
1872
0
            p_subtitle->i_stop = VLC_TICK_0 +
1873
0
                    vlc_tick_from_sec( (f2 + p_props->jss.i_time_shift ) / p_props->jss.i_time_resolution );
1874
0
            break;
1875
0
        }
1876
        /* General Directive lines */
1877
        /* Only TIME and SHIFT are supported so far */
1878
0
        else if( s[0] == '#' )
1879
0
        {
1880
0
            int h = 0, m =0, sec = 1, f = 1;
1881
0
            unsigned shift = 1;
1882
0
            int inv = 1;
1883
1884
0
            strcpy( psz_text, s );
1885
1886
0
            switch( toupper( (unsigned char)psz_text[1] ) )
1887
0
            {
1888
0
            case 'S':
1889
0
                 shift = isalpha( (unsigned char)psz_text[2] ) ? 6 : 2 ;
1890
0
                 if ( shift > line_length )
1891
0
                     break;
1892
1893
0
                 if( sscanf( &psz_text[shift], "%d", &h ) )
1894
0
                 {
1895
                     /* Negative shifting */
1896
0
                     if( h < 0 )
1897
0
                     {
1898
0
                         h *= -1;
1899
0
                         inv = -1;
1900
0
                     }
1901
1902
0
                     if( sscanf( &psz_text[shift], "%*d:%d", &m ) )
1903
0
                     {
1904
0
                         if( sscanf( &psz_text[shift], "%*d:%*d:%d", &sec ) )
1905
0
                         {
1906
0
                             sscanf( &psz_text[shift], "%*d:%*d:%*d.%d", &f );
1907
0
                         }
1908
0
                         else
1909
0
                         {
1910
0
                             h = 0;
1911
0
                             sscanf( &psz_text[shift], "%d:%d.%d",
1912
0
                                     &m, &sec, &f );
1913
0
                             m *= inv;
1914
0
                         }
1915
0
                     }
1916
0
                     else
1917
0
                     {
1918
0
                         h = m = 0;
1919
0
                         sscanf( &psz_text[shift], "%d.%d", &sec, &f);
1920
0
                         sec *= inv;
1921
0
                     }
1922
0
                     p_props->jss.i_time_shift = ( ( h * 3600 + m * 60 + sec )
1923
0
                         * p_props->jss.i_time_resolution + f ) * inv;
1924
0
                 }
1925
0
                 break;
1926
1927
0
            case 'T':
1928
0
                shift = isalpha( (unsigned char)psz_text[2] ) ? 8 : 2 ;
1929
0
                if ( shift > line_length )
1930
0
                    break;
1931
1932
0
                sscanf( &psz_text[shift], "%d", &p_props->jss.i_time_resolution );
1933
0
                if( !p_props->jss.i_time_resolution )
1934
0
                    p_props->jss.i_time_resolution = 30;
1935
0
                break;
1936
0
            }
1937
0
            free( psz_orig );
1938
0
            continue;
1939
0
        }
1940
0
        else
1941
            /* Unknown type line, probably a comment */
1942
0
        {
1943
0
            free( psz_orig );
1944
0
            continue;
1945
0
        }
1946
0
    }
1947
1948
0
    while( psz_text[ strlen( psz_text ) - 1 ] == '\\' )
1949
0
    {
1950
0
        const char *s2 = TextGetLine( txt );
1951
1952
0
        if( !s2 )
1953
0
        {
1954
0
            free( psz_orig );
1955
0
            return VLC_EGENERIC;
1956
0
        }
1957
1958
0
        size_t i_len = strlen( s2 );
1959
0
        if( i_len == 0 )
1960
0
            break;
1961
1962
0
        size_t i_old = strlen( psz_text );
1963
1964
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 );
1965
0
        if( !psz_text )
1966
0
             return VLC_ENOMEM;
1967
1968
0
        psz_orig = psz_text;
1969
0
        strcat( psz_text, s2 );
1970
0
    }
1971
1972
    /* Skip the blanks */
1973
0
    while( *psz_text == ' ' || *psz_text == '\t' ) psz_text++;
1974
1975
    /* Parse the directives */
1976
0
    if( isalpha( (unsigned char)*psz_text ) || *psz_text == '[' )
1977
0
    {
1978
0
        while( *psz_text && *psz_text != ' ' )
1979
0
            ++psz_text;
1980
1981
        /* Directives are NOT parsed yet */
1982
        /* This has probably a better place in a decoder ? */
1983
        /* directive = malloc( strlen( psz_text ) + 1 );
1984
           if( sscanf( psz_text, "%s %[^\n\r]", directive, psz_text2 ) == 2 )*/
1985
0
    }
1986
1987
    /* Skip the blanks after directives */
1988
0
    while( *psz_text == ' ' || *psz_text == '\t' ) psz_text++;
1989
1990
    /* Clean all the lines from inline comments and other stuffs */
1991
0
    psz_orig2 = calloc( strlen( psz_text) + 1, 1 );
1992
0
    psz_text2 = psz_orig2;
1993
1994
0
    for( ; *psz_text != '\0' && *psz_text != '\n' && *psz_text != '\r'; )
1995
0
    {
1996
0
        switch( *psz_text )
1997
0
        {
1998
0
        case '{':
1999
0
            p_props->jss.i_comment++;
2000
0
            break;
2001
0
        case '}':
2002
0
            if( p_props->jss.i_comment )
2003
0
            {
2004
0
                p_props->jss.i_comment = 0;
2005
0
                if( (*(psz_text + 1 ) ) == ' ' ) psz_text++;
2006
0
            }
2007
0
            break;
2008
0
        case '~':
2009
0
            if( !p_props->jss.i_comment )
2010
0
            {
2011
0
                *psz_text2 = ' ';
2012
0
                psz_text2++;
2013
0
            }
2014
0
            break;
2015
0
        case ' ':
2016
0
        case '\t':
2017
0
            if( (*(psz_text + 1 ) ) == ' ' || (*(psz_text + 1 ) ) == '\t' )
2018
0
                break;
2019
0
            if( !p_props->jss.i_comment )
2020
0
            {
2021
0
                *psz_text2 = ' ';
2022
0
                psz_text2++;
2023
0
            }
2024
0
            break;
2025
0
        case '\\':
2026
0
            if( (*(psz_text + 1 ) ) == 'n' )
2027
0
            {
2028
0
                *psz_text2 = '\n';
2029
0
                psz_text++;
2030
0
                psz_text2++;
2031
0
                break;
2032
0
            }
2033
0
            if( ( toupper((unsigned char)*(psz_text + 1 ) ) == 'C' ) ||
2034
0
                    ( toupper((unsigned char)*(psz_text + 1 ) ) == 'F' ) )
2035
0
            {
2036
0
                psz_text++;
2037
0
                break;
2038
0
            }
2039
0
            if( (*(psz_text + 1 ) ) == 'B' || (*(psz_text + 1 ) ) == 'b' ||
2040
0
                (*(psz_text + 1 ) ) == 'I' || (*(psz_text + 1 ) ) == 'i' ||
2041
0
                (*(psz_text + 1 ) ) == 'U' || (*(psz_text + 1 ) ) == 'u' ||
2042
0
                (*(psz_text + 1 ) ) == 'D' || (*(psz_text + 1 ) ) == 'N' )
2043
0
            {
2044
0
                psz_text++;
2045
0
                break;
2046
0
            }
2047
0
            if( (*(psz_text + 1 ) ) == '~' || (*(psz_text + 1 ) ) == '{' ||
2048
0
                (*(psz_text + 1 ) ) == '\\' )
2049
0
                psz_text++;
2050
0
            else if( ( *(psz_text + 1 ) == '\r' ||  *(psz_text + 1 ) == '\n' ) &&
2051
0
                     *(psz_text + 1 ) != '\0' )
2052
0
            {
2053
0
                psz_text++;
2054
0
            }
2055
0
            break;
2056
0
        default:
2057
0
            if( !p_props->jss.i_comment )
2058
0
            {
2059
0
                *psz_text2 = *psz_text;
2060
0
                psz_text2++;
2061
0
            }
2062
0
        }
2063
0
        psz_text++;
2064
0
    }
2065
2066
0
    p_subtitle->psz_text = psz_orig2;
2067
0
    msg_Dbg( p_obj, "%s", p_subtitle->psz_text );
2068
0
    free( psz_orig );
2069
0
    return VLC_SUCCESS;
2070
0
}
2071
2072
static int ParsePSB( vlc_object_t *p_obj, subs_properties_t *p_props,
2073
                     text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2074
0
{
2075
0
    VLC_UNUSED(p_obj);
2076
0
    VLC_UNUSED(p_props);
2077
0
    VLC_UNUSED( i_idx );
2078
2079
0
    char *psz_text;
2080
0
    int i;
2081
2082
0
    for( ;; )
2083
0
    {
2084
0
        int h1, m1, s1;
2085
0
        int h2, m2, s2;
2086
0
        const char *s = TextGetLine( txt );
2087
2088
0
        if( !s )
2089
0
            return VLC_EGENERIC;
2090
2091
0
        psz_text = malloc( strlen( s ) + 1 );
2092
0
        if( !psz_text )
2093
0
            return VLC_ENOMEM;
2094
2095
0
        if( sscanf( s, "{%d:%d:%d}{%d:%d:%d}%[^\r\n]",
2096
0
                    &h1, &m1, &s1, &h2, &m2, &s2, psz_text ) == 7 )
2097
0
        {
2098
0
            p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 );
2099
0
            p_subtitle->i_stop  = VLC_TICK_0 + vlc_tick_from_sec( h2 * 3600 + m2 * 60 + s2 );
2100
0
            break;
2101
0
        }
2102
0
        free( psz_text );
2103
0
    }
2104
2105
    /* replace | by \n */
2106
0
    for( i = 0; psz_text[i] != '\0'; i++ )
2107
0
    {
2108
0
        if( psz_text[i] == '|' )
2109
0
            psz_text[i] = '\n';
2110
0
    }
2111
0
    p_subtitle->psz_text = psz_text;
2112
0
    return VLC_SUCCESS;
2113
0
}
2114
2115
static int64_t ParseRealTime( char *psz, int *h, int *m, int *s, int *f )
2116
0
{
2117
0
    if( *psz == '\0' ) return 0;
2118
0
    if( sscanf( psz, "%d:%d:%d.%d", h, m, s, f ) == 4 ||
2119
0
            sscanf( psz, "%d:%d.%d", m, s, f ) == 3 ||
2120
0
            sscanf( psz, "%d.%d", s, f ) == 2 ||
2121
0
            sscanf( psz, "%d:%d", m, s ) == 2 ||
2122
0
            sscanf( psz, "%d", s ) == 1 )
2123
0
    {
2124
0
        return vlc_tick_from_sec((( *h * 60 + *m ) * 60 ) + *s )
2125
0
               + VLC_TICK_FROM_MS(*f * 10);
2126
0
    }
2127
0
    else return VLC_EGENERIC;
2128
0
}
2129
2130
static int ParseRealText( vlc_object_t *p_obj, subs_properties_t *p_props,
2131
                          text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2132
0
{
2133
0
    VLC_UNUSED(p_obj);
2134
0
    VLC_UNUSED(p_props);
2135
0
    VLC_UNUSED( i_idx );
2136
0
    char *psz_text = NULL;
2137
2138
0
    for( ;; )
2139
0
    {
2140
0
        int h1 = 0, m1 = 0, s1 = 0, f1 = 0;
2141
0
        int h2 = 0, m2 = 0, s2 = 0, f2 = 0;
2142
0
        const char *s = TextGetLine( txt );
2143
0
        free( psz_text );
2144
2145
0
        if( !s )
2146
0
            return VLC_EGENERIC;
2147
2148
0
        psz_text = malloc( strlen( s ) + 1 );
2149
0
        if( !psz_text )
2150
0
            return VLC_ENOMEM;
2151
2152
        /* Find the good beginning. This removes extra spaces at the beginning
2153
           of the line.*/
2154
0
        char *psz_temp = strcasestr( s, "<time");
2155
0
        if( psz_temp != NULL )
2156
0
        {
2157
0
            char psz_end[12], psz_begin[12];
2158
            /* Line has begin and end */
2159
0
            if( ( sscanf( psz_temp,
2160
0
                  "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\" %*[e|E]nd=\"%11[^\"]%*[^>]%[^\n\r]",
2161
0
                            psz_begin, psz_end, psz_text) != 3 ) &&
2162
                    /* Line has begin and no end */
2163
0
                    ( sscanf( psz_temp,
2164
0
                              "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\"%*[^>]%[^\n\r]",
2165
0
                              psz_begin, psz_text ) != 2) )
2166
                /* Line is not recognized */
2167
0
            {
2168
0
                continue;
2169
0
            }
2170
2171
            /* Get the times */
2172
0
            int64_t i_time = ParseRealTime( psz_begin, &h1, &m1, &s1, &f1 );
2173
0
            p_subtitle->i_start = VLC_TICK_0 + (i_time >= 0 ? i_time : 0);
2174
2175
0
            i_time = ParseRealTime( psz_end, &h2, &m2, &s2, &f2 );
2176
0
            p_subtitle->i_stop = VLC_TICK_0 + (i_time >= 0 ? i_time : -1);
2177
0
            break;
2178
0
        }
2179
0
    }
2180
2181
    /* Get the following Lines */
2182
0
    size_t i_old = strlen( psz_text );
2183
0
    for( ;; )
2184
0
    {
2185
0
        const char *s = TextGetLine( txt );
2186
2187
0
        if( !s )
2188
0
        {
2189
0
            free( psz_text );
2190
0
            return VLC_EGENERIC;
2191
0
        }
2192
2193
0
        size_t i_len = strlen( s );
2194
0
        if( i_len == 0 ) break;
2195
2196
0
        if( strcasestr( s, "<time" ) ||
2197
0
            strcasestr( s, "<clear/") )
2198
0
        {
2199
0
            TextPreviousLine( txt );
2200
0
            break;
2201
0
        }
2202
2203
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
2204
0
        if( !psz_text )
2205
0
            return VLC_ENOMEM;
2206
2207
0
        memcpy( &psz_text[i_old], s, i_len );
2208
0
        psz_text[i_old + i_len + 0] = '\n';
2209
0
        i_old += i_len + 1;
2210
0
    }
2211
2212
0
    psz_text[i_old] = '\0';
2213
    /* Remove the starting ">" that remained after the sscanf */
2214
0
    memmove( &psz_text[0], &psz_text[1], strlen( psz_text ) );
2215
2216
0
    p_subtitle->psz_text = psz_text;
2217
2218
0
    return VLC_SUCCESS;
2219
0
}
2220
2221
static int ParseDKS( vlc_object_t *p_obj, subs_properties_t *p_props,
2222
                     text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2223
0
{
2224
0
    VLC_UNUSED(p_obj);
2225
0
    VLC_UNUSED(p_props);
2226
0
    VLC_UNUSED( i_idx );
2227
2228
0
    char *psz_text;
2229
2230
0
    for( ;; )
2231
0
    {
2232
0
        int h1, m1, s1;
2233
0
        int h2, m2, s2;
2234
0
        char *s = TextGetLine( txt );
2235
2236
0
        if( !s )
2237
0
            return VLC_EGENERIC;
2238
2239
0
        psz_text = malloc( strlen( s ) + 1 );
2240
0
        if( !psz_text )
2241
0
            return VLC_ENOMEM;
2242
2243
0
        if( sscanf( s, "[%d:%d:%d]%[^\r\n]",
2244
0
                    &h1, &m1, &s1, psz_text ) == 4 )
2245
0
        {
2246
0
            p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 );
2247
2248
0
            s = TextGetLine( txt );
2249
0
            if( !s )
2250
0
            {
2251
0
                free( psz_text );
2252
0
                return VLC_EGENERIC;
2253
0
            }
2254
2255
0
            if( sscanf( s, "[%d:%d:%d]", &h2, &m2, &s2 ) == 3 )
2256
0
                p_subtitle->i_stop  = vlc_tick_from_sec(h2 * 3600 + m2 * 60 + s2 );
2257
0
            else
2258
0
                p_subtitle->i_stop  = -1;
2259
0
            break;
2260
0
        }
2261
0
        free( psz_text );
2262
0
    }
2263
2264
    /* replace [br] by \n */
2265
0
    char *p;
2266
0
    while( ( p = strstr( psz_text, "[br]" ) ) )
2267
0
    {
2268
0
        *p++ = '\n';
2269
0
        memmove( p, &p[3], strlen(&p[3])+1 );
2270
0
    }
2271
2272
0
    p_subtitle->psz_text = psz_text;
2273
0
    return VLC_SUCCESS;
2274
0
}
2275
2276
static int ParseSubViewer1( vlc_object_t *p_obj, subs_properties_t *p_props,
2277
                            text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2278
0
{
2279
0
    VLC_UNUSED(p_obj);
2280
0
    VLC_UNUSED(p_props);
2281
0
    VLC_UNUSED( i_idx );
2282
0
    char *psz_text;
2283
2284
0
    for( ;; )
2285
0
    {
2286
0
        int h1, m1, s1;
2287
0
        int h2, m2, s2;
2288
0
        char *s = TextGetLine( txt );
2289
2290
0
        if( !s )
2291
0
            return VLC_EGENERIC;
2292
2293
0
        if( sscanf( s, "[%d:%d:%d]", &h1, &m1, &s1 ) == 3 )
2294
0
        {
2295
0
            p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 );
2296
2297
0
            s = TextGetLine( txt );
2298
0
            if( !s )
2299
0
                return VLC_EGENERIC;
2300
2301
0
            psz_text = strdup( s );
2302
0
            if( !psz_text )
2303
0
                return VLC_ENOMEM;
2304
2305
0
            s = TextGetLine( txt );
2306
0
            if( !s )
2307
0
            {
2308
0
                free( psz_text );
2309
0
                return VLC_EGENERIC;
2310
0
            }
2311
2312
0
            if( sscanf( s, "[%d:%d:%d]", &h2, &m2, &s2 ) == 3 )
2313
0
                p_subtitle->i_stop  = vlc_tick_from_sec( h2 * 3600 + m2 * 60 + s2 );
2314
0
            else
2315
0
                p_subtitle->i_stop  = -1;
2316
2317
0
            break;
2318
0
        }
2319
0
    }
2320
2321
0
    p_subtitle->psz_text = psz_text;
2322
2323
0
    return VLC_SUCCESS;
2324
0
}
2325
2326
static int ParseCommonSBV( vlc_object_t *p_obj, subs_properties_t *p_props,
2327
                           text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2328
0
{
2329
0
    VLC_UNUSED(p_obj);
2330
0
    VLC_UNUSED( i_idx );
2331
0
    VLC_UNUSED( p_props );
2332
0
    char        *psz_text;
2333
2334
0
    for( ;; )
2335
0
    {
2336
0
        const char *s = TextGetLine( txt );
2337
0
        int h1 = 0, m1 = 0, s1 = 0, d1 = 0;
2338
0
        int h2 = 0, m2 = 0, s2 = 0, d2 = 0;
2339
2340
0
        if( !s )
2341
0
            return VLC_EGENERIC;
2342
2343
0
        if( sscanf( s,"%d:%d:%d.%d,%d:%d:%d.%d",
2344
0
                    &h1, &m1, &s1, &d1,
2345
0
                    &h2, &m2, &s2, &d2 ) == 8 )
2346
0
        {
2347
0
            p_subtitle->i_start = vlc_tick_from_sec( h1 * 3600 + m1 * 60 + s1 ) +
2348
0
                                  VLC_TICK_FROM_MS( d1 ) + VLC_TICK_0;
2349
2350
0
            p_subtitle->i_stop  = vlc_tick_from_sec( h2 * 3600 + m2 * 60 + s2 ) +
2351
0
                                  VLC_TICK_FROM_MS( d2 ) + VLC_TICK_0;
2352
0
            if( p_subtitle->i_start < p_subtitle->i_stop )
2353
0
                break;
2354
0
        }
2355
0
    }
2356
2357
    /* Now read text until an empty line */
2358
0
    size_t i_old = 0;
2359
0
    psz_text = NULL;
2360
0
    for( ;; )
2361
0
    {
2362
0
        const char *s = TextGetLine( txt );
2363
0
        size_t i_len;
2364
2365
0
        i_len = s ? strlen( s ) : 0;
2366
0
        if( i_len <= 0 )
2367
0
        {
2368
0
            if (psz_text)
2369
0
                psz_text[i_old] = '\0';
2370
0
            p_subtitle->psz_text = psz_text;
2371
0
            return VLC_SUCCESS;
2372
0
        }
2373
2374
0
        psz_text = realloc_or_free( psz_text, i_old + i_len + 1 + 1 );
2375
0
        if( !psz_text )
2376
0
            return VLC_ENOMEM;
2377
2378
0
        memcpy( &psz_text[i_old], s, i_len );
2379
0
        psz_text[i_old + i_len + 0] = '\n';
2380
0
        i_old += i_len + 1;
2381
0
    }
2382
0
}
2383
2384
static int ParseSCC( vlc_object_t *p_obj, subs_properties_t *p_props,
2385
                     text_t *txt, subtitle_t *p_subtitle, size_t i_idx )
2386
0
{
2387
0
    VLC_UNUSED(p_obj);
2388
0
    VLC_UNUSED( i_idx );
2389
0
    VLC_UNUSED( p_props );
2390
2391
0
    static const struct rates
2392
0
    {
2393
0
        unsigned val;
2394
0
        vlc_rational_t rate;
2395
0
        bool b_drop_allowed;
2396
0
    } framerates[] = {
2397
0
        { 2398, { 24000, 1001 }, false },
2398
0
        { 2400, { 24, 1 },       false },
2399
0
        { 2500, { 25, 1 },       false },
2400
0
        { 2997, { 30000, 1001 }, true }, /* encoding rate */
2401
0
        { 3000, { 30, 1 },       false },
2402
0
        { 5000, { 50, 1 },       false },
2403
0
        { 5994, { 60000, 1001 }, true },
2404
0
        { 6000, { 60, 1 },       false },
2405
0
    };
2406
0
    const struct rates *p_rate = &framerates[3];
2407
0
    float f_fps = var_GetFloat( p_obj, "sub-original-fps" );
2408
0
    if( f_fps > 1.0 )
2409
0
    {
2410
0
        for( size_t i=0; i<ARRAY_SIZE(framerates); i++ )
2411
0
        {
2412
0
            if( (unsigned)(f_fps * 100) == framerates[i].val )
2413
0
            {
2414
0
                p_rate = &framerates[i];
2415
0
                break;
2416
0
            }
2417
0
        }
2418
0
    }
2419
2420
0
    for( ;; )
2421
0
    {
2422
0
        const char *psz_line = TextGetLine( txt );
2423
0
        if( !psz_line )
2424
0
            return VLC_EGENERIC;
2425
2426
0
        unsigned h, m, s, f;
2427
0
        char c;
2428
0
        if( sscanf( psz_line, "%u:%u:%u%c%u ", &h, &m, &s, &c, &f ) != 5 ||
2429
0
                ( c != ':' && c != ';' ) )
2430
0
            continue;
2431
2432
        /* convert everything to seconds */
2433
0
        uint64_t i_frames = h * 3600 + m * 60 + s;
2434
2435
0
        if( c == ';' && p_rate->b_drop_allowed ) /* dropframe */
2436
0
        {
2437
            /* convert to frame # to be accurate between inter drop drift
2438
             * of 18 frames see http://andrewduncan.net/timecodes/ */
2439
0
            const unsigned i_mins = h * 60 + m;
2440
0
            i_frames = i_frames * p_rate[+1].rate.num + f
2441
0
                    - (p_rate[+1].rate.den * 2 * (i_mins - i_mins % 10));
2442
0
        }
2443
0
        else
2444
0
        {
2445
            /* convert to frame # at 29.97 */
2446
0
            i_frames = i_frames * framerates[3].rate.num / framerates[3].rate.den + f;
2447
0
        }
2448
0
        p_subtitle->i_start = VLC_TICK_0 + vlc_tick_from_sec(i_frames)*
2449
0
                                         p_rate->rate.den / p_rate->rate.num;
2450
0
        p_subtitle->i_stop = -1;
2451
2452
0
        const char *psz_text = strchr( psz_line, '\t' );
2453
0
        if( !psz_text && !(psz_text = strchr( psz_line, ' ' )) )
2454
0
            continue;
2455
2456
0
        if ( psz_text[1] == '\0' )
2457
0
            continue;
2458
2459
0
        p_subtitle->psz_text = strdup( psz_text + 1 );
2460
0
        if( !p_subtitle->psz_text )
2461
0
            return VLC_ENOMEM;
2462
2463
0
        break;
2464
0
    }
2465
2466
0
    return VLC_SUCCESS;
2467
0
}
2468
2469
/* Tries to extract language from common filename patterns PATH/filename.LANG.ext
2470
   and PATH/Subs/x_LANG.ext (where 'x' is an integer). */
2471
static char * get_language_from_filename( const char * psz_sub_file )
2472
0
{
2473
0
    char *psz_ret = NULL;
2474
0
    char *psz_tmp;
2475
2476
0
    if( !psz_sub_file )
2477
0
        return NULL;
2478
2479
    /* Remove path */
2480
0
    const char *psz_fname = strrchr( psz_sub_file, DIR_SEP_CHAR );
2481
0
    psz_fname = (psz_fname == NULL) ? psz_sub_file : psz_fname + 1;
2482
2483
0
    char *psz_work = strdup( psz_fname );
2484
0
    if( !psz_work )
2485
0
        return NULL;
2486
2487
0
    psz_tmp = strrchr( psz_work, '.' ); /* Find extension */
2488
0
    if( psz_tmp )
2489
0
    {
2490
0
        psz_tmp[0] = '\0'; /* Remove it */
2491
2492
        /* Get substr after next last period - hopefully our language string */
2493
0
        psz_tmp = strrchr( psz_work, '.' );
2494
        /* Otherwise try substr after last underscore for alternate pattern */
2495
0
        if( !psz_tmp )
2496
0
            psz_tmp = strchr( psz_work, '_' );
2497
2498
0
        if( psz_tmp )
2499
0
            psz_ret = strdup(++psz_tmp);
2500
0
    }
2501
2502
0
    free( psz_work );
2503
0
    return psz_ret;
2504
0
}
2505
2506
#ifdef ENABLE_TEST
2507
static void test_subtitle_ParseSubRipTimingValue(void)
2508
{
2509
    fprintf(stderr, "\n# %s:\n", __func__);
2510
2511
    struct test_timing_value
2512
    {
2513
        const char *str;
2514
        vlc_tick_t value;
2515
    };
2516
2517
    static const struct test_timing_value timing_values_success[] =
2518
    {
2519
        { "0:0:0,0",        VLC_TICK_0 },
2520
        { "0:0:0.0",        VLC_TICK_0 },
2521
        { "0:0:0",          VLC_TICK_0 },
2522
    };
2523
2524
    struct test_sized_timing_value
2525
    {
2526
        const char *str;
2527
        vlc_tick_t value;
2528
        size_t length;
2529
    };
2530
2531
    static const struct test_sized_timing_value sized_timing_values_success[] =
2532
    {
2533
        { "0:0:0,1",        VLC_TICK_0, strlen("0:0:0") },
2534
        { "0:0:0.1",        VLC_TICK_0, strlen("0:0:0") },
2535
    };
2536
2537
    static const char *timing_values_fail[] =
2538
    {
2539
        "0:0",
2540
        "0",
2541
    };
2542
2543
    for (size_t i=0; i<ARRAY_SIZE(timing_values_success); ++i)
2544
    {
2545
        fprintf(stderr, "Checking that %s parses into %" PRId64 "\n",
2546
                timing_values_success[i].str, timing_values_success[i].value);
2547
2548
        vlc_tick_t value;
2549
        int ret = subtitle_ParseSubRipTimingValue(&value,
2550
                timing_values_success[i].str,
2551
                strlen(timing_values_success[i].str));
2552
        fprintf(stderr, " -> %" PRId64 "\n", value);
2553
        assert(ret == VLC_SUCCESS);
2554
        assert(value == timing_values_success[i].value);
2555
    }
2556
2557
    for (size_t i=0; i<ARRAY_SIZE(sized_timing_values_success); ++i)
2558
    {
2559
        fprintf(stderr, "Checking that %s (length=%zu) parses into %" PRId64 "\n",
2560
                sized_timing_values_success[i].str,
2561
                sized_timing_values_success[i].length,
2562
                sized_timing_values_success[i].value);
2563
2564
        vlc_tick_t value;
2565
        int ret = subtitle_ParseSubRipTimingValue(&value,
2566
                sized_timing_values_success[i].str,
2567
                sized_timing_values_success[i].length);
2568
        assert(ret == VLC_SUCCESS);
2569
        fprintf(stderr, " -> %" PRId64 "\n", value);
2570
        assert(value == sized_timing_values_success[i].value);
2571
    }
2572
2573
    for (size_t i=0; i<ARRAY_SIZE(timing_values_fail); ++i)
2574
    {
2575
        fprintf(stderr, "Checking that %s fails to parse\n",
2576
                timing_values_fail[i]);
2577
        vlc_tick_t value;
2578
        int ret = subtitle_ParseSubRipTimingValue(&value,
2579
                timing_values_fail[i], strlen(timing_values_fail[i]));
2580
        (void)value;
2581
        assert(ret != VLC_SUCCESS);
2582
    }
2583
2584
    for (size_t i=0; i<ARRAY_SIZE(timing_values_fail); ++i)
2585
    {
2586
        fprintf(stderr, "Checking that %s fails to parse\n",
2587
                timing_values_fail[i]);
2588
        vlc_tick_t value;
2589
        int ret = subtitle_ParseSubRipTimingValue(&value,
2590
                timing_values_fail[i], strlen(timing_values_fail[i]));
2591
        (void)value;
2592
        assert(ret != VLC_SUCCESS);
2593
    }
2594
}
2595
2596
static void test_subtitle_ParseSubRipTiming(void)
2597
{
2598
    fprintf(stderr, "\n# %s:\n", __func__);
2599
2600
    struct test_timing_value
2601
    {
2602
        const char *str;
2603
        vlc_tick_t left;
2604
        vlc_tick_t right;
2605
    };
2606
2607
    static const struct test_timing_value timing_values_success[] =
2608
    {
2609
        { "0:0:0,0 --> 0:0:0,0",        VLC_TICK_0,     VLC_TICK_0 },
2610
        { "0:0:0.0 --> 0:0:0.0",        VLC_TICK_0,     VLC_TICK_0 },
2611
        { "0:0:0   --> 0:0:0",          VLC_TICK_0,     VLC_TICK_0 },
2612
    };
2613
2614
    static const char *timing_values_fail[] =
2615
    {
2616
        "0:0 --> 0:0",
2617
        "0:0 --> 0:0:0,0",
2618
        "0:0:0,0 --> 0:0",
2619
        "0 -> 0",
2620
    };
2621
2622
    for (size_t i=0; i<ARRAY_SIZE(timing_values_success); ++i)
2623
    {
2624
        fprintf(stderr, "Checking that %s parses into %" PRId64 " --> %" PRId64 "\n",
2625
                timing_values_success[i].str,
2626
                timing_values_success[i].left,
2627
                timing_values_success[i].right);
2628
2629
        subtitle_t sub = { .i_start = VLC_TICK_INVALID, .i_stop = VLC_TICK_INVALID };
2630
        int ret = subtitle_ParseSubRipTiming(&sub, timing_values_success[i].str);
2631
        fprintf(stderr, " -> %" PRId64 " --> %" PRId64 "\n", sub.i_start, sub.i_stop);
2632
        assert(ret == VLC_SUCCESS);
2633
        assert(sub.i_start == timing_values_success[i].left);
2634
        assert(sub.i_stop == timing_values_success[i].right);
2635
    }
2636
2637
    for (size_t i=0; i<ARRAY_SIZE(timing_values_fail); ++i)
2638
    {
2639
        fprintf(stderr, "Checking that %s fails to parse\n",
2640
                timing_values_fail[i]);
2641
        subtitle_t sub = { .i_start = VLC_TICK_INVALID, .i_stop = VLC_TICK_INVALID };
2642
        int ret = subtitle_ParseSubRipTiming(&sub, timing_values_fail[i]);
2643
        (void)sub;
2644
        assert(ret != VLC_SUCCESS);
2645
    }
2646
}
2647
2648
int main(int argc, char **argv)
2649
{
2650
    (void)argc; (void)argv;
2651
    test_subtitle_ParseSubRipTimingValue();
2652
    test_subtitle_ParseSubRipTiming();
2653
2654
    return 0;
2655
}
2656
#endif