subsusf.c 41.9 KB
Newer Older
1
/*****************************************************************************
2
 * subsusf.c : USF subtitles decoder
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2000-2006 VLC authors and VideoLAN
5
 * $Id$
6
 *
7
 * Authors: Bernie Purcell <bitmap@videolan.org>
8
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
9
10
11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
12
13
14
15
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
16
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
19
20
21
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
 *****************************************************************************/
23
24
25
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
26
#include <assert.h>
27

28
#include <vlc_common.h>
29
#include <vlc_plugin.h>
30
#include <vlc_modules.h>
31
32
33
34
35
36
#include <vlc_codec.h>
#include <vlc_input.h>
#include <vlc_charset.h>
#include <vlc_image.h>
#include <vlc_xml.h>
#include <vlc_stream.h>
37

38
/*****************************************************************************
39
 * Module descriptor.
40
41
42
43
 *****************************************************************************/
static int  OpenDecoder   ( vlc_object_t * );
static void CloseDecoder  ( vlc_object_t * );

44
45
46
47
48
49
50
vlc_module_begin ()
    set_capability( "decoder", 40 )
    set_shortname( N_("USFSubs"))
    set_description( N_("USF subtitles decoder") )
    set_callbacks( OpenDecoder, CloseDecoder )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_SCODEC )
51
    /* We inherit subsdec-align and subsdec-formatted from subsdec.c */
52
vlc_module_end ()
53

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
enum
{
    ATTRIBUTE_ALIGNMENT = (1 << 0),
    ATTRIBUTE_X         = (1 << 1),
    ATTRIBUTE_X_PERCENT = (1 << 2),
    ATTRIBUTE_Y         = (1 << 3),
    ATTRIBUTE_Y_PERCENT = (1 << 4),
};

typedef struct
{
    char       *psz_filename;
    picture_t  *p_pic;
} image_attach_t;

typedef struct
{
    char *          psz_stylename; /* The name of the style, no comma's allowed */
    text_style_t    font_style;
    int             i_align;
    int             i_margin_h;
    int             i_margin_v;
    int             i_margin_percent_h;
    int             i_margin_percent_v;
}  ssa_style_t;

struct decoder_sys_t
{
    int                 i_original_height;
    int                 i_original_width;
    int                 i_align;          /* Subtitles alignment on the vout */

    ssa_style_t         **pp_ssa_styles;
    int                 i_ssa_styles;

    image_attach_t      **pp_images;
    int                 i_images;
};

static subpicture_t *DecodeBlock   ( decoder_t *, block_t ** );
static char         *CreatePlainText( char * );
static int           ParseImageAttachments( decoder_t *p_dec );

static subpicture_t        *ParseText     ( decoder_t *, block_t * );
static void                 ParseUSFHeader( decoder_t * );
static subpicture_region_t *ParseUSFString( decoder_t *, char * );
static subpicture_region_t *LoadEmbeddedImage( decoder_t *p_dec, const char *psz_filename, int i_transparent_color );

106
107
108
109
110
111
112
113
114
115
116
/*****************************************************************************
 * OpenDecoder: probe the decoder and return score
 *****************************************************************************
 * Tries to launch a decoder and return score so that the interface is able
 * to chose.
 *****************************************************************************/
static int OpenDecoder( vlc_object_t *p_this )
{
    decoder_t     *p_dec = (decoder_t*)p_this;
    decoder_sys_t *p_sys;

117
    if( p_dec->fmt_in.i_codec != VLC_CODEC_USF )
118
119
120
        return VLC_EGENERIC;

    /* Allocate the memory needed to store the decoder's structure */
121
    if( ( p_dec->p_sys = p_sys = calloc(1, sizeof(decoder_sys_t)) ) == NULL )
122
        return VLC_ENOMEM;
123
124

    p_dec->pf_decode_sub = DecodeBlock;
125
126
    p_dec->fmt_out.i_cat = SPU_ES;
    p_dec->fmt_out.i_codec = 0;
127
128
129
130
131
132
133

    /* init of p_sys */
    TAB_INIT( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
    TAB_INIT( p_sys->i_images, p_sys->pp_images );

    /* USF subtitles are mandated to be UTF-8, so don't need vlc_iconv */

134
    p_sys->i_align = var_CreateGetInteger( p_dec, "subsdec-align" );
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    ParseImageAttachments( p_dec );

    if( var_CreateGetBool( p_dec, "subsdec-formatted" ) )
    {
        if( p_dec->fmt_in.i_extra > 0 )
            ParseUSFHeader( p_dec );
    }

    return VLC_SUCCESS;
}

/****************************************************************************
 * DecodeBlock: the whole thing
 ****************************************************************************
 * This function must be fed with complete subtitles units.
 ****************************************************************************/
static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block )
{
154
155
    subpicture_t *p_spu;
    block_t *p_block;
156

157
158
    if( !pp_block || *pp_block == NULL )
        return NULL;
159

160
    p_block = *pp_block;
161

162
163
164
    p_spu = ParseText( p_dec, p_block );

    block_Release( p_block );
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    *pp_block = NULL;

    return p_spu;
}

/*****************************************************************************
 * CloseDecoder: clean up the decoder
 *****************************************************************************/
static void CloseDecoder( vlc_object_t *p_this )
{
    decoder_t *p_dec = (decoder_t *)p_this;
    decoder_sys_t *p_sys = p_dec->p_sys;

    if( p_sys->pp_ssa_styles )
    {
        int i;
        for( i = 0; i < p_sys->i_ssa_styles; i++ )
        {
            if( !p_sys->pp_ssa_styles[i] )
                continue;

ivoire's avatar
ivoire committed
186
            free( p_sys->pp_ssa_styles[i]->psz_stylename );
basOS G's avatar
basOS G committed
187
            //FIXME: Make font_style a pointer and use text_style_* functions
ivoire's avatar
ivoire committed
188
189
            free( p_sys->pp_ssa_styles[i]->font_style.psz_fontname );
            free( p_sys->pp_ssa_styles[i] );
190
191
192
193
194
195
196
197
198
199
200
201
        }
        TAB_CLEAN( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
    }
    if( p_sys->pp_images )
    {
        int i;
        for( i = 0; i < p_sys->i_images; i++ )
        {
            if( !p_sys->pp_images[i] )
                continue;

            if( p_sys->pp_images[i]->p_pic )
Laurent Aimar's avatar
Laurent Aimar committed
202
                picture_Release( p_sys->pp_images[i]->p_pic );
ivoire's avatar
ivoire committed
203
            free( p_sys->pp_images[i]->psz_filename );
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

            free( p_sys->pp_images[i] );
        }
        TAB_CLEAN( p_sys->i_images, p_sys->pp_images );
    }

    free( p_sys );
}

/*****************************************************************************
 * ParseText: parse an text subtitle packet and send it to the video output
 *****************************************************************************/
static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    subpicture_t *p_spu = NULL;
    char *psz_subtitle = NULL;

    /* We cannot display a subpicture with no date */
223
    if( p_block->i_pts <= VLC_TS_INVALID )
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
    {
        msg_Warn( p_dec, "subtitle without a date" );
        return NULL;
    }

    /* Check validity of packet data */
    /* An "empty" line containing only \0 can be used to force
       and ephemer picture from the screen */
    if( p_block->i_buffer < 1 )
    {
        msg_Warn( p_dec, "no subtitle data" );
        return NULL;
    }

    /* Should be resiliant against bad subtitles */
    psz_subtitle = strndup( (const char *)p_block->p_buffer,
                            p_block->i_buffer );
    if( psz_subtitle == NULL )
        return NULL;

    /* USF Subtitles are mandated to be UTF-8 -- make sure it is */
    if (EnsureUTF8( psz_subtitle ) == NULL)
    {
247
248
        msg_Err( p_dec, "USF subtitles must be in UTF-8 format.\n"
                 "This stream contains USF subtitles which aren't." );
249
250
251
    }

    /* Create the subpicture unit */
252
    p_spu = decoder_NewSubpicture( p_dec, NULL );
253
254
255
    if( !p_spu )
    {
        msg_Warn( p_dec, "can't get spu buffer" );
ivoire's avatar
ivoire committed
256
        free( psz_subtitle );
257
258
259
260
        return NULL;
    }

    /* Decode USF strings */
261
    p_spu->p_region = ParseUSFString( p_dec, psz_subtitle );
262
263
264
265

    p_spu->i_start = p_block->i_pts;
    p_spu->i_stop = p_block->i_pts + p_block->i_length;
    p_spu->b_ephemer = (p_block->i_length == 0);
266
    p_spu->b_absolute = false;
267
268
269
    p_spu->i_original_picture_width = p_sys->i_original_width;
    p_spu->i_original_picture_height = p_sys->i_original_height;

ivoire's avatar
ivoire committed
270
    free( psz_subtitle );
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

    return p_spu;
}

static char *GrabAttributeValue( const char *psz_attribute,
                                 const char *psz_tag_start )
{
    if( psz_attribute && psz_tag_start )
    {
        char *psz_tag_end = strchr( psz_tag_start, '>' );
        char *psz_found   = strcasestr( psz_tag_start, psz_attribute );

        if( psz_found )
        {
            psz_found += strlen( psz_attribute );

            if(( *(psz_found++) == '=' ) &&
               ( *(psz_found++) == '\"' ))
            {
                if( psz_found < psz_tag_end )
                {
                    int   i_len = strcspn( psz_found, "\"" );
                    return strndup( psz_found, i_len );
                }
            }
        }
    }
    return NULL;
}

static ssa_style_t *ParseStyle( decoder_sys_t *p_sys, char *psz_subtitle )
{
basOS G's avatar
basOS G committed
303
    ssa_style_t *p_ssa_style = NULL;
304
305
306
307
308
309
310
311
312
    char        *psz_style = GrabAttributeValue( "style", psz_subtitle );

    if( psz_style )
    {
        int i;

        for( i = 0; i < p_sys->i_ssa_styles; i++ )
        {
            if( !strcmp( p_sys->pp_ssa_styles[i]->psz_stylename, psz_style ) )
basOS G's avatar
basOS G committed
313
                p_ssa_style = p_sys->pp_ssa_styles[i];
314
315
316
        }
        free( psz_style );
    }
basOS G's avatar
basOS G committed
317
    return p_ssa_style;
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
}

static int ParsePositionAttributeList( char *psz_subtitle, int *i_align,
                                       int *i_x, int *i_y )
{
    int   i_mask = 0;

    char *psz_align    = GrabAttributeValue( "alignment", psz_subtitle );
    char *psz_margin_x = GrabAttributeValue( "horizontal-margin", psz_subtitle );
    char *psz_margin_y = GrabAttributeValue( "vertical-margin", psz_subtitle );
    /* -- UNSUPPORTED
    char *psz_relative = GrabAttributeValue( "relative-to", psz_subtitle );
    char *psz_rotate_x = GrabAttributeValue( "rotate-x", psz_subtitle );
    char *psz_rotate_y = GrabAttributeValue( "rotate-y", psz_subtitle );
    char *psz_rotate_z = GrabAttributeValue( "rotate-z", psz_subtitle );
    */

    *i_align = SUBPICTURE_ALIGN_BOTTOM;
    *i_x = 0;
    *i_y = 0;

    if( psz_align )
    {
        if( !strcasecmp( "TopLeft", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
        else if( !strcasecmp( "TopCenter", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_TOP;
        else if( !strcasecmp( "TopRight", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
        else if( !strcasecmp( "MiddleLeft", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_LEFT;
        else if( !strcasecmp( "MiddleCenter", psz_align ) )
            *i_align = 0;
        else if( !strcasecmp( "MiddleRight", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_RIGHT;
        else if( !strcasecmp( "BottomLeft", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
        else if( !strcasecmp( "BottomCenter", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_BOTTOM;
        else if( !strcasecmp( "BottomRight", psz_align ) )
            *i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;

        i_mask |= ATTRIBUTE_ALIGNMENT;
        free( psz_align );
    }
    if( psz_margin_x )
    {
        *i_x = atoi( psz_margin_x );
        if( strchr( psz_margin_x, '%' ) )
            i_mask |= ATTRIBUTE_X_PERCENT;
        else
            i_mask |= ATTRIBUTE_X;

        free( psz_margin_x );
    }
    if( psz_margin_y )
    {
        *i_y = atoi( psz_margin_y );
        if( strchr( psz_margin_y, '%' ) )
            i_mask |= ATTRIBUTE_Y_PERCENT;
        else
            i_mask |= ATTRIBUTE_Y;

        free( psz_margin_y );
    }
    return i_mask;
}

static void SetupPositions( subpicture_region_t *p_region, char *psz_subtitle )
{
    int           i_mask = 0;
    int           i_align;
    int           i_x, i_y;

    i_mask = ParsePositionAttributeList( psz_subtitle, &i_align, &i_x, &i_y );

    if( i_mask & ATTRIBUTE_ALIGNMENT )
        p_region->i_align = i_align;

    /* TODO: Setup % based offsets properly, without adversely affecting
     *       everything else in vlc. Will address with separate patch, to
     *       prevent this one being any more complicated.
     */
    if( i_mask & ATTRIBUTE_X )
        p_region->i_x = i_x;
    else if( i_mask & ATTRIBUTE_X_PERCENT )
        p_region->i_x = 0;

    if( i_mask & ATTRIBUTE_Y )
        p_region->i_y = i_y;
    else if( i_mask & ATTRIBUTE_Y_PERCENT )
        p_region->i_y = 0;
}

static subpicture_region_t *CreateTextRegion( decoder_t *p_dec,
                                              char *psz_subtitle,
                                              int i_len,
                                              int i_sys_align )
{
    decoder_sys_t        *p_sys = p_dec->p_sys;
    subpicture_region_t  *p_text_region;
    video_format_t        fmt;

    /* Create a new subpicture region */
    memset( &fmt, 0, sizeof(video_format_t) );
423
    fmt.i_chroma = VLC_CODEC_TEXT;
424
425
    fmt.i_width = fmt.i_height = 0;
    fmt.i_x_offset = fmt.i_y_offset = 0;
426
    p_text_region = subpicture_region_New( &fmt );
427
428
429

    if( p_text_region != NULL )
    {
basOS G's avatar
basOS G committed
430
        ssa_style_t  *p_ssa_style = NULL;
431

432
        p_ssa_style = ParseStyle( p_sys, psz_subtitle );
basOS G's avatar
basOS G committed
433
        if( !p_ssa_style )
434
435
436
437
438
439
        {
            int i;

            for( i = 0; i < p_sys->i_ssa_styles; i++ )
            {
                if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) )
basOS G's avatar
basOS G committed
440
                    p_ssa_style = p_sys->pp_ssa_styles[i];
441
442
443
            }
        }

basOS G's avatar
basOS G committed
444
        if( p_ssa_style )
445
        {
basOS G's avatar
basOS G committed
446
            msg_Dbg( p_dec, "style is: %s", p_ssa_style->psz_stylename );
447

basOS G's avatar
basOS G committed
448
            p_text_region->i_align = p_ssa_style->i_align;
449
450
451
452
453

            /* TODO: Setup % based offsets properly, without adversely affecting
             *       everything else in vlc. Will address with separate patch,
             *       to prevent this one being any more complicated.

basOS G's avatar
basOS G committed
454
455
                     * p_ssa_style->i_margin_percent_h;
                     * p_ssa_style->i_margin_percent_v;
456
             */
basOS G's avatar
basOS G committed
457
458
            p_text_region->i_x         = p_ssa_style->i_margin_h;
            p_text_region->i_y         = p_ssa_style->i_margin_v;
459
            p_text_region->p_text = text_segment_NewInheritStyle( &p_ssa_style->font_style );
460
461
462
463
464
465
        }
        else
        {
            p_text_region->i_align = SUBPICTURE_ALIGN_BOTTOM | i_sys_align;
            p_text_region->i_x = i_sys_align ? 20 : 0;
            p_text_region->i_y = 10;
466
            p_text_region->p_text = text_segment_New( NULL );
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
        }
        /* Look for position arguments which may override the style-based
         * defaults.
         */
        SetupPositions( p_text_region, psz_subtitle );

        p_text_region->p_next = NULL;
    }
    return p_text_region;
}

static int ParseImageAttachments( decoder_t *p_dec )
{
    decoder_sys_t        *p_sys = p_dec->p_sys;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;
    int                   k = 0;

    if( VLC_SUCCESS != decoder_GetInputAttachments( p_dec, &pp_attachments, &i_attachments_cnt ))
        return VLC_EGENERIC;

    for( k = 0; k < i_attachments_cnt; k++ )
    {
        input_attachment_t *p_attach = pp_attachments[k];

dionoea's avatar
dionoea committed
492
        vlc_fourcc_t type = image_Mime2Fourcc( p_attach->psz_mime );
493
494
495
496
497
498
499
500
501
502
503
504
505

        if( ( type != 0 ) &&
            ( p_attach->i_data > 0 ) &&
            ( p_attach->p_data != NULL ) )
        {
            picture_t         *p_pic = NULL;
            image_handler_t   *p_image;

            p_image = image_HandlerCreate( p_dec );
            if( p_image != NULL )
            {
                block_t   *p_block;

506
                p_block = block_Alloc( p_attach->i_data );
507
508
509
510
511
512
513
514
515
516
517
518

                if( p_block != NULL )
                {
                    video_format_t     fmt_in;
                    video_format_t     fmt_out;

                    memcpy( p_block->p_buffer, p_attach->p_data, p_attach->i_data );

                    memset( &fmt_in,  0, sizeof( video_format_t));
                    memset( &fmt_out, 0, sizeof( video_format_t));

                    fmt_in.i_chroma  = type;
519
                    fmt_out.i_chroma = VLC_CODEC_YUVA;
520
521

                    /* Find a suitable decoder module */
522
                    if( module_exists( "sdl_image" ) )
523
524
525
526
                    {
                        /* ffmpeg thinks it can handle bmp properly but it can't (at least
                         * not all of them), so use sdl_image if it is available */

527
528
                        var_Create( p_dec, "codec", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
                        var_SetString( p_dec, "codec", "sdl_image" );
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
                    }

                    p_pic = image_Read( p_image, p_block, &fmt_in, &fmt_out );
                    var_Destroy( p_dec, "codec" );
                }

                image_HandlerDelete( p_image );
            }
            if( p_pic )
            {
                image_attach_t *p_picture = malloc( sizeof(image_attach_t) );

                if( p_picture )
                {
                    p_picture->psz_filename = strdup( p_attach->psz_name );
                    p_picture->p_pic = p_pic;

                    TAB_APPEND( p_sys->i_images, p_sys->pp_images, p_picture );
                }
            }
        }
        vlc_input_attachment_Delete( pp_attachments[ k ] );
    }
    free( pp_attachments );

    return VLC_SUCCESS;
}

557
558
559
static void ParseUSFHeaderTags( decoder_t *p_dec, xml_reader_t *p_xml_reader )
{
    decoder_sys_t *p_sys = p_dec->p_sys;
560
    const char *node;
basOS G's avatar
basOS G committed
561
    ssa_style_t *p_ssa_style = NULL;
562
563
    int i_style_level = 0;
    int i_metadata_level = 0;
564
    int type;
565

566
    while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
567
    {
568
        switch( type )
569
570
571
572
573
        {
            case XML_READER_ENDELEM:
                switch (i_style_level)
                {
                    case 0:
574
                        if( !strcasecmp( "metadata", node ) && (i_metadata_level == 1) )
575
576
577
                            i_metadata_level--;
                        break;
                    case 1:
578
                        if( !strcasecmp( "styles", node ) )
579
580
581
                            i_style_level--;
                        break;
                    case 2:
582
                        if( !strcasecmp( "style", node ) )
583
                        {
basOS G's avatar
basOS G committed
584
                            TAB_APPEND( p_sys->i_ssa_styles, p_sys->pp_ssa_styles, p_ssa_style );
585

basOS G's avatar
basOS G committed
586
                            p_ssa_style = NULL;
587
588
589
590
591
592
                            i_style_level--;
                        }
                        break;
                }
                break;

593
594
            case XML_READER_STARTELEM:
                if( !strcasecmp( "metadata", node ) && (i_style_level == 0) )
595
                    i_metadata_level++;
596
                else if( !strcasecmp( "resolution", node ) &&
597
                         ( i_metadata_level == 1) )
598
                {
599
600
                    const char *attr, *val;
                    while( (attr = xml_ReaderNextAttr( p_xml_reader, &val )) )
601
                    {
602
                        if( !strcasecmp( "x", attr ) )
603
                            p_sys->i_original_width = atoi( val );
604
                        else if( !strcasecmp( "y", attr ) )
605
                            p_sys->i_original_height = atoi( val );
606
607
                    }
                }
608
                else if( !strcasecmp( "styles", node ) && (i_style_level == 0) )
609
610
611
                {
                    i_style_level++;
                }
612
                else if( !strcasecmp( "style", node ) && (i_style_level == 1) )
613
614
615
                {
                    i_style_level++;

basOS G's avatar
basOS G committed
616
                    p_ssa_style = calloc( 1, sizeof(ssa_style_t) );
617
                    if( unlikely(!p_ssa_style) )
Laurent Aimar's avatar
Laurent Aimar committed
618
                        return;
619
620
621
622
623
                    /* All styles are supposed to default to Default, and then
                     * one or more settings are over-ridden.
                     * At the moment this only effects styles defined AFTER
                     * Default in the XML
                     */
624
                    for( int i = 0; i < p_sys->i_ssa_styles; i++ )
625
626
627
628
629
                    {
                        if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) )
                        {
                            ssa_style_t *p_default_style = p_sys->pp_ssa_styles[i];

basOS G's avatar
basOS G committed
630
631
632
633
634
                            memcpy( p_ssa_style, p_default_style, sizeof( ssa_style_t ) );
                            //FIXME: Make font_style a pointer. Actually we double copy some data here,
                            //   we use text_style_Copy to avoid copying psz_fontname, though .
                            text_style_Copy( &p_ssa_style->font_style, &p_default_style->font_style );
                            p_ssa_style->psz_stylename = NULL;
635
636
637
                        }
                    }

638
639
                    const char *attr, *val;
                    while( (attr = xml_ReaderNextAttr( p_xml_reader, &val )) )
640
                    {
641
                        if( !strcasecmp( "name", attr ) )
642
                        {
643
                            free( p_ssa_style->psz_stylename );
644
                            p_ssa_style->psz_stylename = strdup( val );
645
646
647
                        }
                    }
                }
648
                else if( !strcasecmp( "fontstyle", node ) && (i_style_level == 2) )
649
                {
650
651
                    const char *attr, *val;
                    while( (attr = xml_ReaderNextAttr( p_xml_reader, &val )) )
652
                    {
653
                        if( !strcasecmp( "face", attr ) )
654
                        {
655
                            free( p_ssa_style->font_style.psz_fontname );
656
                            p_ssa_style->font_style.psz_fontname = strdup( val );
657
658
659
                        }
                        else if( !strcasecmp( "size", attr ) )
                        {
660
                            if( ( *val == '+' ) || ( *val == '-' ) )
661
                            {
662
                                int i_value = atoi( val );
663
664
665
666
667
668
669
670

                                if( ( i_value >= -5 ) && ( i_value <= 5 ) )
                                    p_ssa_style->font_style.i_font_size  +=
                                       ( i_value * p_ssa_style->font_style.i_font_size ) / 10;
                                else if( i_value < -5 )
                                    p_ssa_style->font_style.i_font_size  = - i_value;
                                else if( i_value > 5 )
                                    p_ssa_style->font_style.i_font_size  = i_value;
671
                            }
672
                            else
673
                                p_ssa_style->font_style.i_font_size  = atoi( val );
674
675
676
                        }
                        else if( !strcasecmp( "italic", attr ) )
                        {
677
                            if( !strcasecmp( "yes", val ))
678
679
680
681
682
683
                                p_ssa_style->font_style.i_style_flags |= STYLE_ITALIC;
                            else
                                p_ssa_style->font_style.i_style_flags &= ~STYLE_ITALIC;
                        }
                        else if( !strcasecmp( "weight", attr ) )
                        {
684
                            if( !strcasecmp( "bold", val ))
685
686
687
688
689
690
                                p_ssa_style->font_style.i_style_flags |= STYLE_BOLD;
                            else
                                p_ssa_style->font_style.i_style_flags &= ~STYLE_BOLD;
                        }
                        else if( !strcasecmp( "underline", attr ) )
                        {
691
                            if( !strcasecmp( "yes", val ))
692
693
694
695
696
697
                                p_ssa_style->font_style.i_style_flags |= STYLE_UNDERLINE;
                            else
                                p_ssa_style->font_style.i_style_flags &= ~STYLE_UNDERLINE;
                        }
                        else if( !strcasecmp( "color", attr ) )
                        {
698
                            if( *val == '#' )
699
                            {
700
                                unsigned long col = strtol(val+1, NULL, 16);
701
702
                                 p_ssa_style->font_style.i_font_color = (col & 0x00ffffff);
                                 p_ssa_style->font_style.i_font_alpha = (col >> 24) & 0xff;
703
                            }
704
705
706
                        }
                        else if( !strcasecmp( "outline-color", attr ) )
                        {
707
                            if( *val == '#' )
708
                            {
709
                                unsigned long col = strtol(val+1, NULL, 16);
710
711
                                p_ssa_style->font_style.i_outline_color = (col & 0x00ffffff);
                                p_ssa_style->font_style.i_outline_alpha = (col >> 24) & 0xff;
712
                            }
713
714
715
                        }
                        else if( !strcasecmp( "outline-level", attr ) )
                        {
716
                            p_ssa_style->font_style.i_outline_width = atoi( val );
717
718
719
                        }
                        else if( !strcasecmp( "shadow-color", attr ) )
                        {
720
                            if( *val == '#' )
721
                            {
722
                                unsigned long col = strtol(val+1, NULL, 16);
723
724
                                p_ssa_style->font_style.i_shadow_color = (col & 0x00ffffff);
                                p_ssa_style->font_style.i_shadow_alpha = (col >> 24) & 0xff;
725
                            }
726
727
728
                        }
                        else if( !strcasecmp( "shadow-level", attr ) )
                        {
729
                            p_ssa_style->font_style.i_shadow_width = atoi( val );
730
731
732
                        }
                        else if( !strcasecmp( "back-color", attr ) )
                        {
733
                            if( *val == '#' )
734
                            {
735
                                unsigned long col = strtol(val+1, NULL, 16);
736
737
                                p_ssa_style->font_style.i_karaoke_background_color = (col & 0x00ffffff);
                                p_ssa_style->font_style.i_karaoke_background_alpha = (col >> 24) & 0xff;
738
739
                            }
                        }
740
741
                        else if( !strcasecmp( "spacing", attr ) )
                        {
742
                            p_ssa_style->font_style.i_spacing = atoi( val );
743
                        }
744
745
                    }
                }
746
                else if( !strcasecmp( "position", node ) && (i_style_level == 2) )
747
                {
748
749
                    const char *attr, *val;
                    while( (attr = xml_ReaderNextAttr( p_xml_reader, &val )) )
750
                    {
751
                        if( !strcasecmp( "alignment", attr ) )
752
                        {
753
                            if( !strcasecmp( "TopLeft", val ) )
754
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
755
                            else if( !strcasecmp( "TopCenter", val ) )
756
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP;
757
                            else if( !strcasecmp( "TopRight", val ) )
758
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
759
                            else if( !strcasecmp( "MiddleLeft", val ) )
760
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_LEFT;
761
                            else if( !strcasecmp( "MiddleCenter", val ) )
762
                                p_ssa_style->i_align = 0;
763
                            else if( !strcasecmp( "MiddleRight", val ) )
764
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_RIGHT;
765
                            else if( !strcasecmp( "BottomLeft", val ) )
766
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
767
                            else if( !strcasecmp( "BottomCenter", val ) )
768
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM;
769
                            else if( !strcasecmp( "BottomRight", val ) )
770
771
772
773
                                p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
                        }
                        else if( !strcasecmp( "horizontal-margin", attr ) )
                        {
774
                            if( strchr( val, '%' ) )
775
776
                            {
                                p_ssa_style->i_margin_h = 0;
777
                                p_ssa_style->i_margin_percent_h = atoi( val );
778
779
                            }
                            else
780
                            {
781
                                p_ssa_style->i_margin_h = atoi( val );
782
                                p_ssa_style->i_margin_percent_h = 0;
783
                            }
784
785
786
                        }
                        else if( !strcasecmp( "vertical-margin", attr ) )
                        {
787
                            if( strchr( val, '%' ) )
788
                            {
789
                                p_ssa_style->i_margin_v = 0;
790
                                p_ssa_style->i_margin_percent_v = atoi( val );
791
                            }
792
                            else
793
                            {
794
                                p_ssa_style->i_margin_v = atoi( val );
795
                                p_ssa_style->i_margin_percent_v = 0;
796
797
                            }
                        }
798
799
                    }
                }
800
                break;
801
802
        }
    }
basOS G's avatar
basOS G committed
803
    free( p_ssa_style );
804
}
805

806

807

808
static subpicture_region_t *ParseUSFString( decoder_t *p_dec,
809
                                            char *psz_subtitle )
810
811
812
813
{
    decoder_sys_t        *p_sys = p_dec->p_sys;
    subpicture_region_t  *p_region_first = NULL;
    subpicture_region_t  *p_region_upto  = p_region_first;
814
815
816
817
818

    while( *psz_subtitle )
    {
        if( *psz_subtitle == '<' )
        {
819
            char *psz_end = NULL;
820

821

822
            if(( !strncasecmp( psz_subtitle, "<karaoke ", 9 )) ||
823
824
825
826
827
                    ( !strncasecmp( psz_subtitle, "<karaoke>", 9 )))
            {
                psz_end = strcasestr( psz_subtitle, "</karaoke>" );

                if( psz_end )
828
                {
829
                    subpicture_region_t  *p_text_region;
830

831
832
833
834
835
836
                    psz_end += strcspn( psz_end, ">" ) + 1;

                    p_text_region = CreateTextRegion( p_dec,
                                                      psz_subtitle,
                                                      psz_end - psz_subtitle,
                                                      p_sys->i_align );
837

838
                    if( !p_region_first )
839
                    {
840
841
842
843
844
845
                        p_region_first = p_region_upto = p_text_region;
                    }
                    else if( p_text_region )
                    {
                        p_region_upto->p_next = p_text_region;
                        p_region_upto = p_region_upto->p_next;
846
847
848
                    }
                }
            }
849
850
            else if(( !strncasecmp( psz_subtitle, "<image ", 7 )) ||
                    ( !strncasecmp( psz_subtitle, "<image>", 7 )))
851
            {
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
                subpicture_region_t *p_image_region = NULL;

                char *psz_end = strcasestr( psz_subtitle, "</image>" );
                char *psz_content = strchr( psz_subtitle, '>' );
                int   i_transparent = -1;

                /* If a colorkey parameter is specified, then we have to map
                 * that index in the picture through as transparent (it is
                 * required by the USF spec but is also recommended that if the
                 * creator really wants a transparent colour that they use a
                 * type like PNG that properly supports it; this goes doubly
                 * for VLC because the pictures are stored internally in YUV
                 * and the resulting colour-matching may not produce the
                 * desired results.)
                 */
                char *psz_tmp = GrabAttributeValue( "colorkey", psz_subtitle );
                if( psz_tmp )
869
                {
870
871
872
                    if( *psz_tmp == '#' )
                        i_transparent = strtol( psz_tmp + 1, NULL, 16 ) & 0x00ffffff;
                    free( psz_tmp );
873
                }
874
                if( psz_content && ( psz_content < psz_end ) )
875
                {
876
877
878
                    char *psz_filename = strndup( &psz_content[1], psz_end - &psz_content[1] );
                    if( psz_filename )
                    {
879
                        p_image_region = LoadEmbeddedImage( p_dec,
880
881
882
                                            psz_filename, i_transparent );
                        free( psz_filename );
                    }
883
                }
884
885
886
887

                if( psz_end ) psz_end += strcspn( psz_end, ">" ) + 1;

                if( p_image_region )
888
                {
889
890
891
                    SetupPositions( p_image_region, psz_subtitle );

                    p_image_region->p_next   = NULL;
892
                }
893
                if( !p_region_first )
894
                {
895
                    p_region_first = p_region_upto = p_image_region;
896
                }
897
                else if( p_image_region )
898
                {
899
900
                    p_region_upto->p_next = p_image_region;
                    p_region_upto = p_region_upto->p_next;
901
902
                }
            }
903
904
905
906
            else
            {
                subpicture_region_t  *p_text_region;

907
908
909
910
                if( psz_end )
                    psz_end += strcspn( psz_end, ">" ) + 1;
                else
                    psz_end = psz_subtitle + strlen( psz_subtitle );
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932

                p_text_region = CreateTextRegion( p_dec,
                                                  psz_subtitle,
                                                  psz_end - psz_subtitle,
                                                  p_sys->i_align );

                if( p_text_region )
                {
                    free( p_text_region->p_text->psz_text );
                    p_text_region->p_text->psz_text = CreatePlainText( psz_subtitle );
                }

                if( !p_region_first )
                {
                    p_region_first = p_region_upto = p_text_region;
                }
                else if( p_text_region )
                {
                    p_region_upto->p_next = p_text_region;
                    p_region_upto = p_region_upto->p_next;
                }
            }
933
934
            if( psz_end )
                psz_subtitle = psz_end - 1;
935

936
            psz_subtitle += strcspn( psz_subtitle, ">" );
937
938
        }

939
        psz_subtitle++;
940
941
    }

942
    return p_region_first;
943
}
944

945
946
947
/*****************************************************************************
 * ParseUSFHeader: Retrieve global formatting information etc
 *****************************************************************************/
948
static void ParseUSFHeader( decoder_t *p_dec )
949
{
950
951
    stream_t      *p_sub = NULL;
    xml_reader_t  *p_xml_reader = NULL;
952

953
954
955
    p_sub = stream_MemoryNew( VLC_OBJECT(p_dec),
                              p_dec->fmt_in.p_extra,
                              p_dec->fmt_in.i_extra,
956
                              true );
957
958
    if( !p_sub )
        return;
959

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
960
    p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
961
    if( likely(p_xml_reader) )
962
    {
963
        const char *node;
964

965
966
967
968
        /* Look for Root Node */
        if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM
         && !strcasecmp( "usfsubtitles", node ) )
            ParseUSFHeaderTags( p_dec, p_xml_reader );
969

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
970
        xml_ReaderDelete( p_xml_reader );
971
    }
972
    stream_Delete( p_sub );
973
}
974

975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
/* Function now handles tags which has attribute values, and tries
 * to deal with &' commands too. It no longer modifies the string
 * in place, so that the original text can be reused
 */
static char *StripTags( char *psz_subtitle )
{
    char *psz_text_start;
    char *psz_text;

    psz_text = psz_text_start = malloc( strlen( psz_subtitle ) + 1 );
    if( !psz_text_start )
        return NULL;

    while( *psz_subtitle )
    {
        /* Mask out any pre-existing LFs in the subtitle */
        if( *psz_subtitle == '\n' )
            *psz_subtitle = ' ';
993

994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
        if( *psz_subtitle == '<' )
        {
            if( strncasecmp( psz_subtitle, "<br/>", 5 ) == 0 )
                *psz_text++ = '\n';

            psz_subtitle += strcspn( psz_subtitle, ">" );
        }
        else if( *psz_subtitle == '&' )
        {
            if( !strncasecmp( psz_subtitle, "&lt;", 4 ))
            {
                *psz_text++ = '<';
                psz_subtitle += strcspn( psz_subtitle, ";" );
            }
            else if( !strncasecmp( psz_subtitle, "&gt;", 4 ))
            {
                *psz_text++ = '>';
                psz_subtitle += strcspn( psz_subtitle, ";" );
            }
            else if( !strncasecmp( psz_subtitle, "&amp;", 5 ))
            {
                *psz_text++ = '&';
                psz_subtitle += strcspn( psz_subtitle, ";" );
            }
            else if( !strncasecmp( psz_subtitle, "&quot;", 6 ))
            {
                *psz_text++ = '\"';
                psz_subtitle += strcspn( psz_subtitle, ";" );
            }
            else
            {
                /* Assume it is just a normal ampersand */
                *psz_text++ = '&';
            }
        }
        else
        {
            *psz_text++ = *psz_subtitle;
        }

1034
1035
1036
        /* Security fix: Account for the case where input ends early */
        if( *psz_subtitle == '\0' ) break;

1037
1038
        psz_subtitle++;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1039
    *psz_text++ = '\0';
1040

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1041
1042
    char *psz = realloc( psz_text_start, psz_text - psz_text_start );
    return likely(psz != NULL) ? psz : psz_text_start;
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
}

/* Turn a HTML subtitle, turn into a plain-text version,
 *  complete with sensible whitespace compaction
 */

static char *CreatePlainText( char *psz_subtitle )
{
    char *psz_text = StripTags( psz_subtitle );
    char *s;

    if( !psz_text )
        return NULL;

    s = strpbrk( psz_text, "\t\r\n " );
    while( s )
    {
        int   k;
        char  spc = ' ';
        int   i_whitespace = strspn( s, "\t\r\n " );

        /* Favour '\n' over other whitespaces - if one of these
         * occurs in the whitespace use a '\n' as our value,
         * otherwise just use a ' '
         */
        for( k = 0; k < i_whitespace; k++ )
            if( s[k] == '\n' ) spc = '\n';

        if( i_whitespace > 1 )
        {
            memmove( &s[1],
                     &s[i_whitespace],
                     strlen( s ) - i_whitespace + 1 );
        }
        *s++ = spc;

        s = strpbrk( s, "\t\r\n " );
    }
    return psz_text;
}

/****************************************************************************
 * download and resize image located at psz_url
 ***************************************************************************/
static subpicture_region_t *LoadEmbeddedImage( decoder_t *p_dec,
                                               const char *psz_filename,
                                               int i_transparent_color )
{
    decoder_sys_t         *p_sys = p_dec->p_sys;
    subpicture_region_t   *p_region;
    video_format_t         fmt_out;
    picture_t             *p_pic = NULL;

1096
    for( int k = 0; k < p_sys->i_images; k++ )
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
    {
        if( p_sys->pp_images &&
            !strcmp( p_sys->pp_images[k]->psz_filename, psz_filename ) )
        {
            p_pic = p_sys->pp_images[k]->p_pic;
            break;
        }
    }

    if( !p_pic )
    {
        msg_Err( p_dec, "Unable to read image %s", psz_filename );
        return NULL;
    }

    /* Display the feed's image */
    memset( &fmt_out, 0, sizeof( video_format_t));

1115
    fmt_out.i_chroma = VLC_CODEC_YUVA;
1116
1117
    fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
    fmt_out.i_width =
1118
        fmt_out.i_visible_width = p_pic->format.i_visible_width;
1119
    fmt_out.i_height =
1120
        fmt_out.i_visible_height = p_pic->format.i_visible_height;
1121

1122
    p_region = subpicture_region_New( &fmt_out );
1123
1124
1125
1126
1127
    if( !p_region )
    {
        msg_Err( p_dec, "cannot allocate SPU region" );
        return NULL;
    }
1128
    assert( p_pic->format.i_chroma == VLC_CODEC_YUVA );
1129
1130
    /* FIXME the copy is probably not needed anymore */
    picture_CopyPixels( p_region->p_picture, p_pic );
1131
1132
1133
1134
1135
1136
1137

    /* This isn't the best way to do this - if you really want transparency, then
     * you're much better off using an image type that supports it like PNG. The
     * spec requires this support though.
     */
    if( i_transparent_color > 0 )
    {
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
        int i_r = ( i_transparent_color >> 16 ) & 0xff;
        int i_g = ( i_transparent_color >>  8 ) & 0xff;
        int i_b = ( i_transparent_color       ) & 0xff;

        /* FIXME it cannot work as the yuv conversion code will probably NOT match
         * this one  */
        int i_y = ( ( (  66 * i_r + 129 * i_g +  25 * i_b + 128 ) >> 8 ) + 16 );
        int i_u =   ( ( -38 * i_r -  74 * i_g + 112 * i_b + 128 ) >> 8 ) + 128 ;
        int i_v =   ( ( 112 * i_r -  94 * i_g -  18 * i_b + 128 ) >> 8 ) + 128 ;

1148
        assert( p_region->fmt.i_chroma == VLC_CODEC_YUVA );
1149
        for( unsigned int y = 0; y < p_region->fmt.i_height; y++ )
1150
        {
1151
            for( unsigned int x = 0; x < p_region->fmt.i_width; x++ )
1152
            {
1153
1154
1155
                if( p_region->p_picture->Y_PIXELS[y*p_region->p_picture->Y_PITCH + x] != i_y ||
                    p_region->p_picture->U_PIXELS[y*p_region->p_picture->U_PITCH + x] != i_u ||
                    p_region->p_picture->V_PIXELS[y*p_region->p_picture->V_PITCH + x] != i_v )
1156
                    continue;
1157
                p_region->p_picture->A_PIXELS[y*p_region->p_picture->A_PITCH + x] = 0;
1158

1159
1160
1161
1162
1163
            }
        }
    }
    return p_region;
}