Coverage Report

Created: 2023-03-26 06:11

/src/vlc/modules/demux/xiph_metadata.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * xiph_metadata.h: Vorbis Comment parser
3
 *****************************************************************************
4
 * Copyright © 2008-2013 VLC authors and VideoLAN
5
 *
6
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
7
 *          Jean-Baptiste Kempf <jb@videolan.org>
8
 *
9
 * This program is free software; you can redistribute it and/or modify it
10
 * under the terms of the GNU Lesser General Public License as published by
11
 * the Free Software Foundation; either version 2.1 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with this program; if not, write to the Free Software Foundation,
21
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
 *****************************************************************************/
23
24
#ifdef HAVE_CONFIG_H
25
# include "config.h"
26
#endif
27
28
#include <assert.h>
29
30
#include <vlc_common.h>
31
#include <vlc_charset.h>
32
#include <vlc_strings.h>
33
#include <vlc_arrays.h>
34
#include <vlc_input.h>
35
#include "xiph_metadata.h"
36
37
input_attachment_t* ParseFlacPicture( const uint8_t *p_data, size_t size,
38
    int i_attachments, int *i_cover_score, int *i_cover_idx )
39
0
{
40
    /* TODO: Merge with ID3v2 copy in modules/meta_engine/taglib.cpp. */
41
0
    static const char pi_cover_score[] = {
42
0
        0,  /* Other */
43
0
        5,  /* 32x32 PNG image that should be used as the file icon */
44
0
        4,  /* File icon of a different size or format. */
45
0
        20, /* Front cover image of the album. */
46
0
        19, /* Back cover image of the album. */
47
0
        13, /* Inside leaflet page of the album. */
48
0
        18, /* Image from the album itself. */
49
0
        17, /* Picture of the lead artist or soloist. */
50
0
        16, /* Picture of the artist or performer. */
51
0
        14, /* Picture of the conductor. */
52
0
        15, /* Picture of the band or orchestra. */
53
0
        9,  /* Picture of the composer. */
54
0
        8,  /* Picture of the lyricist or text writer. */
55
0
        7,  /* Picture of the recording location or studio. */
56
0
        10, /* Picture of the artists during recording. */
57
0
        11, /* Picture of the artists during performance. */
58
0
        6,  /* Picture from a movie or video related to the track. */
59
0
        1,  /* Picture of a large, coloured fish. */
60
0
        12, /* Illustration related to the track. */
61
0
        3,  /* Logo of the band or performer. */
62
0
        2   /* Logo of the publisher (record company). */
63
0
    };
64
65
0
    uint32_t type, len;
66
67
0
    if( size < 8 )
68
0
        return NULL;
69
0
#define RM(x) \
70
0
    do { \
71
0
        assert(size >= (x)); \
72
0
        size -= (x); \
73
0
        p_data += (x); \
74
0
    } while (0)
75
76
0
    type = GetDWBE( p_data );
77
0
    RM(4);
78
0
    len = GetDWBE( p_data );
79
0
    RM(4);
80
81
0
    if( size < len )
82
0
        return NULL;
83
84
0
    char *mime = strndup( (const char *)p_data, len );
85
0
    if( unlikely(mime == NULL) )
86
0
        return NULL;
87
0
    RM(len);
88
89
0
    if( size < 4 )
90
0
    {
91
0
        free( mime );
92
0
        return NULL;
93
0
    }
94
95
0
    len = GetDWBE( p_data );
96
0
    RM(4);
97
98
0
    if( size < len )
99
0
    {
100
0
        free( mime );
101
0
        return NULL;
102
0
    }
103
104
0
    input_attachment_t *p_attachment = NULL;
105
0
    char *description = strndup( (const char *)p_data, len );
106
0
    if( unlikely(description == NULL) )
107
0
        goto error;
108
0
    RM(len);
109
110
0
    EnsureUTF8( description );
111
112
0
    if( size < 20 )
113
0
        goto error;
114
115
0
    RM(4 * 4); /* skip */
116
117
0
    len = GetDWBE( p_data );
118
0
    RM(4);
119
120
0
    if( size < len )
121
0
        goto error;
122
123
    /* printf( "Picture type=%"PRIu32" mime=%s description='%s' "
124
               "file length=%zu\n", type, mime, description, len ); */
125
126
0
    char name[7 + (sizeof (i_attachments) * 3) + 4 + 1];
127
128
0
    snprintf( name, sizeof (name), "picture%u", i_attachments );
129
130
0
    if( !strcasecmp( mime, "image/jpeg" ) )
131
0
        strcat( name, ".jpg" );
132
0
    else if( !strcasecmp( mime, "image/png" ) )
133
0
        strcat( name, ".png" );
134
135
0
    p_attachment = vlc_input_attachment_New( name, mime, description, p_data,
136
0
                                             size /* XXX: len instead? */ );
137
138
0
    if( type < ARRAY_SIZE(pi_cover_score) &&
139
0
        *i_cover_score < pi_cover_score[type] )
140
0
    {
141
0
        *i_cover_idx = i_attachments;
142
0
        *i_cover_score = pi_cover_score[type];
143
0
    }
144
145
0
error:
146
0
    free( mime );
147
0
    free( description );
148
0
    return p_attachment;
149
0
}
150
151
#undef RM
152
#define RM(x) \
153
1
    do { \
154
1
        i_data -= (x); \
155
1
        p_data += (x); \
156
1
    } while (0)
157
158
159
typedef struct chapters_array_t
160
{
161
    unsigned int i_size;
162
    seekpoint_t ** pp_chapters;
163
} chapters_array_t;
164
165
static seekpoint_t * getChapterEntry( unsigned int i_index, chapters_array_t *p_array )
166
0
{
167
0
    if ( i_index > 4096 ) return NULL;
168
0
    if ( i_index >= p_array->i_size )
169
0
    {
170
0
        unsigned int i_newsize = p_array->i_size;
171
0
        while( i_index >= i_newsize ) i_newsize += 50;
172
173
0
        if ( !p_array->pp_chapters )
174
0
        {
175
0
            p_array->pp_chapters = calloc( i_newsize, sizeof( seekpoint_t * ) );
176
0
            if ( !p_array->pp_chapters ) return NULL;
177
0
            p_array->i_size = i_newsize;
178
0
        } else {
179
0
            seekpoint_t **tmp = calloc( i_newsize, sizeof( seekpoint_t * ) );
180
0
            if ( !tmp ) return NULL;
181
0
            memcpy( tmp, p_array->pp_chapters, p_array->i_size * sizeof( seekpoint_t * ) );
182
0
            free( p_array->pp_chapters );
183
0
            p_array->pp_chapters = tmp;
184
0
            p_array->i_size = i_newsize;
185
0
        }
186
0
    }
187
0
    if ( !p_array->pp_chapters[i_index] )
188
0
        p_array->pp_chapters[i_index] = vlc_seekpoint_New();
189
0
    return p_array->pp_chapters[i_index];
190
0
}
191
192
0
#define XIPHMETA_Title        (1 << 0)
193
0
#define XIPHMETA_Artist       (1 << 1)
194
0
#define XIPHMETA_Genre        (1 << 2)
195
0
#define XIPHMETA_Copyright    (1 << 3)
196
0
#define XIPHMETA_Album        (1 << 4)
197
0
#define XIPHMETA_TrackNum     (1 << 5)
198
0
#define XIPHMETA_Description  (1 << 6)
199
0
#define XIPHMETA_Rating       (1 << 7)
200
0
#define XIPHMETA_Date         (1 << 8)
201
0
#define XIPHMETA_Language     (1 << 9)
202
0
#define XIPHMETA_Publisher    (1 << 10)
203
0
#define XIPHMETA_EncodedBy    (1 << 11)
204
0
#define XIPHMETA_TrackTotal   (1 << 12)
205
206
static char * xiph_ExtractCueSheetMeta( const char *psz_line,
207
                                        const char *psz_tag, int i_tag,
208
                                        bool b_quoted )
209
0
{
210
0
    if( !strncasecmp( psz_line, psz_tag, i_tag ) )
211
0
    {
212
0
        if( !b_quoted )
213
0
            return strdup( &psz_line[i_tag] );
214
215
        /* Unquote string value */
216
0
        char *psz_value = malloc( strlen( psz_line ) - i_tag + 1 );
217
0
        if( psz_value )
218
0
        {
219
0
            char *psz_out = psz_value;
220
0
            psz_line += i_tag;
221
0
            bool b_escaped = false;
222
0
            while( *psz_line )
223
0
            {
224
0
                switch( *psz_line )
225
0
                {
226
0
                    case '\\':
227
0
                        if( b_escaped )
228
0
                        {
229
0
                            b_escaped = false;
230
0
                            *(psz_out++) = *psz_line;
231
0
                        }
232
0
                        else
233
0
                        {
234
0
                            b_escaped = true;
235
0
                        }
236
0
                        break;
237
0
                    case '"':
238
0
                        if( b_escaped )
239
0
                        {
240
0
                            b_escaped = false;
241
0
                            *(psz_out++) = *psz_line;
242
0
                        }
243
0
                        break;
244
0
                    default:
245
0
                        *(psz_out++) = *psz_line;
246
0
                        break;
247
0
                }
248
0
                psz_line++;
249
0
            }
250
0
            *psz_out = 0;
251
0
            return psz_value;
252
0
        }
253
0
    }
254
0
    return NULL;
255
0
}
256
257
static void xiph_ParseCueSheetMeta( unsigned *pi_flags, vlc_meta_t *p_meta,
258
                                    const char *psz_line,
259
                                    int *pi_seekpoint, seekpoint_t ***ppp_seekpoint,
260
                                    seekpoint_t **pp_tmppoint, bool *pb_valid )
261
0
{
262
0
    VLC_UNUSED(pi_seekpoint);
263
0
    VLC_UNUSED(ppp_seekpoint);
264
265
0
    seekpoint_t *p_seekpoint = *pp_tmppoint;
266
0
    char *psz_string;
267
268
0
#define TRY_EXTRACT_CUEMETA(var, string, quoted) \
269
0
    if( !(*pi_flags & XIPHMETA_##var) &&\
270
0
         ( psz_string = xiph_ExtractCueSheetMeta( psz_line, string, sizeof(string) - 1, quoted ) ) )\
271
0
    {\
272
0
        vlc_meta_Set( p_meta, vlc_meta_##var, psz_string );\
273
0
        free( psz_string );\
274
0
        *pi_flags |= XIPHMETA_##var;\
275
0
    }
276
277
0
    TRY_EXTRACT_CUEMETA(Title, "TITLE \"", true)
278
0
    else TRY_EXTRACT_CUEMETA(Genre, "REM GENRE ", false)
279
0
    else TRY_EXTRACT_CUEMETA(Date, "REM DATE ", false)
280
0
    else TRY_EXTRACT_CUEMETA(Artist, "PERFORMER \"", true)
281
0
    else if( !strncasecmp( psz_line, "  TRACK ", 8 ) )
282
0
    {
283
0
        if( p_seekpoint )
284
0
        {
285
0
            if( *pb_valid )
286
0
                TAB_APPEND( *pi_seekpoint, *ppp_seekpoint, p_seekpoint );
287
0
            else
288
0
                vlc_seekpoint_Delete( p_seekpoint );
289
0
            *pb_valid = false;
290
0
        }
291
0
        *pp_tmppoint = p_seekpoint = vlc_seekpoint_New();
292
0
    }
293
0
    else if( p_seekpoint && !strncasecmp( psz_line, "    INDEX 01 ", 13 ) )
294
0
    {
295
0
        unsigned m, s, f;
296
0
        if( sscanf( &psz_line[13], "%u:%u:%u", &m, &s, &f ) == 3 )
297
0
        {
298
0
            p_seekpoint->i_time_offset = vlc_tick_from_sec(m * 60 + s) + vlc_tick_from_samples(f, 75);
299
0
            *pb_valid = true;
300
0
        }
301
0
    }
302
0
    else if( p_seekpoint && !p_seekpoint->psz_name )
303
0
    {
304
0
        p_seekpoint->psz_name = xiph_ExtractCueSheetMeta( psz_line, "    TITLE \"", 11, true );
305
0
    }
306
0
}
307
308
static void xiph_ParseCueSheet( unsigned *pi_flags, vlc_meta_t *p_meta,
309
                                const char *p_data, int i_data,
310
                                int *pi_seekpoint, seekpoint_t ***ppp_seekpoint )
311
0
{
312
0
    seekpoint_t *p_seekpoint = NULL;
313
0
    bool b_valid = false;
314
315
0
    const char *p_head = p_data;
316
0
    const char *p_tail = p_head;
317
0
    while( p_tail < p_data + i_data )
318
0
    {
319
0
        if( *p_tail == 0x0D )
320
0
        {
321
0
            char *psz = strndup( p_head, p_tail - p_head );
322
0
            if( psz )
323
0
            {
324
0
                xiph_ParseCueSheetMeta( pi_flags, p_meta, psz,
325
0
                                        pi_seekpoint, ppp_seekpoint,
326
0
                                        &p_seekpoint, &b_valid );
327
0
                free( psz );
328
0
            }
329
0
            if( *(++p_tail) == 0x0A )
330
0
                p_tail++;
331
0
            p_head = p_tail;
332
0
        }
333
0
        else
334
0
        {
335
0
            p_tail++;
336
0
        }
337
0
    }
338
339
340
0
    if( p_seekpoint )
341
0
    {
342
0
        if( b_valid )
343
0
            TAB_APPEND( *pi_seekpoint, *ppp_seekpoint, p_seekpoint );
344
0
        else
345
0
            vlc_seekpoint_Delete( p_seekpoint );
346
0
    }
347
0
}
348
349
void vorbis_ParseComment( es_format_t *p_fmt, vlc_meta_t **pp_meta,
350
        const uint8_t *p_data, size_t i_data,
351
        int *i_attachments, input_attachment_t ***attachments,
352
        int *i_cover_score, int *i_cover_idx,
353
        int *i_seekpoint, seekpoint_t ***ppp_seekpoint,
354
        float (* ppf_replay_gain)[AUDIO_REPLAY_GAIN_MAX],
355
        float (* ppf_replay_peak)[AUDIO_REPLAY_GAIN_MAX] )
356
1
{
357
1
    if( i_data < 8 )
358
0
        return;
359
360
1
    uint32_t vendor_length = GetDWLE(p_data); RM(4);
361
362
1
    if( vendor_length > i_data )
363
1
        return; /* invalid length */
364
365
0
    RM(vendor_length); /* TODO: handle vendor payload */
366
367
0
    if( i_data < 4 )
368
0
        return;
369
370
0
    uint32_t i_comment = GetDWLE(p_data); RM(4);
371
372
0
    if( i_comment > i_data || i_comment == 0 )
373
0
        return; /* invalid length */
374
375
    /* */
376
0
    vlc_meta_t *p_meta = *pp_meta;
377
0
    if( !p_meta )
378
0
        *pp_meta = p_meta = vlc_meta_New();
379
380
0
    if( unlikely( !p_meta ) )
381
0
        return;
382
383
    /* */
384
0
    unsigned hasMetaFlags = 0;
385
386
0
    chapters_array_t chapters_array = { 0, NULL };
387
388
0
    for( ; i_comment > 0 && i_data >= 4; i_comment-- )
389
0
    {
390
0
        uint32_t comment_size = GetDWLE(p_data); RM(4);
391
392
0
        if( comment_size > i_data )
393
0
            break;
394
395
0
        if( comment_size == 0 )
396
0
            continue;
397
398
0
        char* psz_comment = malloc( comment_size + 1 );
399
400
0
        if( unlikely( !psz_comment ) )
401
0
            goto next_comment;
402
403
0
        memcpy( psz_comment, p_data, comment_size );
404
0
        psz_comment[comment_size] = '\0';
405
406
0
#define IF_EXTRACT(txt,var) \
407
0
    if( !strncasecmp(psz_comment, txt, strlen(txt)) ) \
408
0
    { \
409
0
        size_t key_length = strlen(txt); \
410
0
        EnsureUTF8( psz_comment + key_length ); \
411
0
        const char *oldval = vlc_meta_Get( p_meta, vlc_meta_ ## var ); \
412
0
        if( oldval && (hasMetaFlags & XIPHMETA_##var)) \
413
0
        { \
414
0
            char * newval; \
415
0
            if( asprintf( &newval, "%s,%s", oldval, &psz_comment[key_length] ) == -1 ) \
416
0
                newval = NULL; \
417
0
            vlc_meta_Set( p_meta, vlc_meta_ ## var, newval ); \
418
0
            free( newval ); \
419
0
        } \
420
0
        else \
421
0
            vlc_meta_Set( p_meta, vlc_meta_ ## var, &psz_comment[key_length] ); \
422
0
        hasMetaFlags |= XIPHMETA_##var; \
423
0
    }
424
425
0
#define IF_EXTRACT_ONCE(txt,var) \
426
0
    if( !strncasecmp(psz_comment, txt, strlen(txt)) && !(hasMetaFlags & XIPHMETA_##var) ) \
427
0
    { \
428
0
        vlc_meta_Set( p_meta, vlc_meta_ ## var, &psz_comment[strlen(txt)] ); \
429
0
        hasMetaFlags |= XIPHMETA_##var; \
430
0
    }
431
432
0
        IF_EXTRACT("TITLE=", Title )
433
0
        else IF_EXTRACT("ARTIST=", Artist )
434
0
        else IF_EXTRACT("GENRE=", Genre )
435
0
        else IF_EXTRACT("COPYRIGHT=", Copyright )
436
0
        else IF_EXTRACT("ALBUM=", Album )
437
0
        else if( !(hasMetaFlags & XIPHMETA_TrackNum) && !strncasecmp(psz_comment, "TRACKNUMBER=", strlen("TRACKNUMBER=" ) ) )
438
0
        {
439
            /* Yeah yeah, such a clever idea, let's put xx/xx inside TRACKNUMBER
440
             * Oh, and let's not use TRACKTOTAL or TOTALTRACKS... */
441
0
            short unsigned u_track, u_total;
442
0
            int nb_values = sscanf( &psz_comment[strlen("TRACKNUMBER=")], "%hu/%hu", &u_track, &u_total );
443
0
            if( nb_values >= 1 )
444
0
            {
445
0
                char str[6];
446
0
                snprintf(str, 6, "%u", u_track);
447
0
                vlc_meta_Set( p_meta, vlc_meta_TrackNumber, str );
448
0
                hasMetaFlags |= XIPHMETA_TrackNum;
449
0
                if( nb_values >= 2 )
450
0
                {
451
0
                    snprintf(str, 6, "%u", u_total);
452
0
                    vlc_meta_Set( p_meta, vlc_meta_TrackTotal, str );
453
0
                    hasMetaFlags |= XIPHMETA_TrackTotal;
454
0
                }
455
0
            }
456
0
        }
457
0
        else IF_EXTRACT_ONCE("TRACKTOTAL=", TrackTotal )
458
0
        else IF_EXTRACT_ONCE("TOTALTRACKS=", TrackTotal )
459
0
        else IF_EXTRACT("DESCRIPTION=", Description )
460
0
        else IF_EXTRACT("COMMENT=", Description )
461
0
        else IF_EXTRACT("COMMENTS=", Description )
462
0
        else IF_EXTRACT("RATING=", Rating )
463
0
        else IF_EXTRACT("DATE=", Date )
464
0
        else if( !strncasecmp(psz_comment, "LANGUAGE=", strlen("LANGUAGE=") ) )
465
0
        {
466
0
            IF_EXTRACT("LANGUAGE=",Language)
467
0
            if( p_fmt )
468
0
            {
469
0
                free( p_fmt->psz_language );
470
0
                p_fmt->psz_language = strdup(&psz_comment[strlen("LANGUAGE=")]);
471
0
            }
472
0
        }
473
0
        else IF_EXTRACT("ORGANIZATION=", Publisher )
474
0
        else IF_EXTRACT("ENCODER=", EncodedBy )
475
0
        else if( !strncasecmp( psz_comment, "METADATA_BLOCK_PICTURE=", strlen("METADATA_BLOCK_PICTURE=")))
476
0
        {
477
0
            if( attachments == NULL )
478
0
                goto next_comment;
479
480
0
            uint8_t *p_picture;
481
0
            size_t i_size = vlc_b64_decode_binary( &p_picture, &psz_comment[strlen("METADATA_BLOCK_PICTURE=")]);
482
0
            input_attachment_t *p_attachment = ParseFlacPicture( p_picture,
483
0
                i_size, *i_attachments, i_cover_score, i_cover_idx );
484
0
            free( p_picture );
485
0
            if( p_attachment )
486
0
            {
487
0
                TAB_APPEND_CAST( (input_attachment_t**),
488
0
                    *i_attachments, *attachments, p_attachment );
489
0
            }
490
0
        }
491
0
        else if ( ppf_replay_gain && ppf_replay_peak && !strncmp(psz_comment, "REPLAYGAIN_", 11) )
492
0
        {
493
0
            char *p = strchr( psz_comment, '=' );
494
0
            if (!p) goto next_comment;
495
0
            if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_GAIN=", 22) )
496
0
            {
497
0
                (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p );
498
0
            }
499
0
            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_GAIN=", 22) )
500
0
            {
501
0
                (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p );
502
0
            }
503
0
            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_PEAK=", 22) )
504
0
            {
505
0
                (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p );
506
0
            }
507
0
            else if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_PEAK=", 22) )
508
0
            {
509
0
                (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p );
510
0
            }
511
0
        }
512
0
        else if( !strncasecmp(psz_comment, "CHAPTER", 7) )
513
0
        {
514
0
            unsigned int i_chapt;
515
0
            seekpoint_t *p_seekpoint = NULL;
516
517
0
            for( int i = 0; psz_comment[i] && psz_comment[i] != '='; i++ )
518
0
                if( psz_comment[i] >= 'a' && psz_comment[i] <= 'z' )
519
0
                    psz_comment[i] -= 'a' - 'A';
520
521
0
            if( strstr( psz_comment, "NAME=" ) &&
522
0
                    sscanf( psz_comment, "CHAPTER%uNAME=", &i_chapt ) == 1 )
523
0
            {
524
0
                char *p = strchr( psz_comment, '=' );
525
0
                p_seekpoint = getChapterEntry( i_chapt, &chapters_array );
526
0
                if ( !p || ! p_seekpoint ) goto next_comment;
527
0
                EnsureUTF8( ++p );
528
0
                if ( ! p_seekpoint->psz_name )
529
0
                    p_seekpoint->psz_name = strdup( p );
530
0
            }
531
0
            else if( sscanf( psz_comment, "CHAPTER%u=", &i_chapt ) == 1 )
532
0
            {
533
0
                unsigned int h, m, s, ms;
534
0
                char *p = strchr( psz_comment, '=' );
535
0
                if( p && sscanf( ++p, "%u:%u:%u.%u", &h, &m, &s, &ms ) == 4 )
536
0
                {
537
0
                    p_seekpoint = getChapterEntry( i_chapt, &chapters_array );
538
0
                    if ( ! p_seekpoint ) goto next_comment;
539
0
                    p_seekpoint->i_time_offset = vlc_tick_from_sec(h * 3600 + m * 60 + s) + VLC_TICK_FROM_MS(ms);
540
0
                }
541
0
            }
542
0
        }
543
0
        else if( !strncasecmp(psz_comment, "cuesheet=", 9) )
544
0
        {
545
0
            EnsureUTF8( &psz_comment[9] );
546
0
            xiph_ParseCueSheet( &hasMetaFlags, p_meta, &psz_comment[9], comment_size - 9,
547
0
                                i_seekpoint, ppp_seekpoint );
548
0
        }
549
0
        else if( strchr( psz_comment, '=' ) )
550
0
        {
551
            /* generic (PERFORMER/LICENSE/ORGANIZATION/LOCATION/CONTACT/ISRC,
552
             * undocumented tags and replay gain ) */
553
0
            char *p = strchr( psz_comment, '=' );
554
0
            *p++ = '\0';
555
0
            EnsureUTF8( p );
556
557
0
            for( int i = 0; psz_comment[i]; i++ )
558
0
                if( psz_comment[i] >= 'a' && psz_comment[i] <= 'z' )
559
0
                    psz_comment[i] -= 'a' - 'A';
560
561
0
            vlc_meta_AddExtra( p_meta, psz_comment, p );
562
0
        }
563
0
#undef IF_EXTRACT
564
0
next_comment:
565
0
        free( psz_comment );
566
0
        RM( comment_size );
567
0
    }
568
0
#undef RM
569
570
0
    for ( unsigned int i=0; i<chapters_array.i_size; i++ )
571
0
    {
572
0
        if ( !chapters_array.pp_chapters[i] ) continue;
573
0
        TAB_APPEND_CAST( (seekpoint_t**), *i_seekpoint, *ppp_seekpoint,
574
0
                         chapters_array.pp_chapters[i] );
575
0
    }
576
0
    free( chapters_array.pp_chapters );
577
0
}
578
579
const char *FindKateCategoryName( const char *psz_tag )
580
0
{
581
0
    for( size_t i = 0; i < sizeof(Katei18nCategories)/sizeof(Katei18nCategories[0]); i++ )
582
0
    {
583
0
        if( !strcmp( psz_tag, Katei18nCategories[i].psz_tag ) )
584
0
            return Katei18nCategories[i].psz_i18n;
585
0
    }
586
0
    return N_("Unknown category");
587
0
}
588