Changeset 8e3c06e49467efff48dea25c24c0baab7126b79f

Show
Ignore:
Timestamp:
17/06/07 15:47:00 (1 year ago)
Author:
Laurent Aimar <fenrir@videolan.org>
git-committer:
Laurent Aimar <fenrir@videolan.org> 1182088020 +0000
git-parent:

[1e900bf8cab23a83e70f5b56d2e9295a359cffee]

git-author:
Laurent Aimar <fenrir@videolan.org> 1182088020 +0000
Message:

Added APEv1/2 support to id3tag.c
Parse TXXX fields (id3)
Parse RVA2 and convert them to replay gain (id3)
(I cannot put APE parsing into a new module as id3 and ape can be found
in the same file, this means you need libid3tag)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • modules/meta_engine/id3tag.c

    r53a7683 r8e3c06e  
    11/***************************************************************************** 
    2  * id3tag.c: id3 tag parser/skipper based on libid3tag 
     2 * id3tag.c: id3/ape tag parser/skipper based on libid3tag 
    33 ***************************************************************************** 
    44 * Copyright (C) 2002-2004 the VideoLAN team 
     
    4545 * Local prototypes 
    4646 *****************************************************************************/ 
    47 static int  ParseID3Tags ( vlc_object_t * ); 
     47static int  ParseTags ( vlc_object_t * ); 
    4848 
    4949/***************************************************************************** 
     
    5151 *****************************************************************************/ 
    5252vlc_module_begin(); 
    53     set_description( _("ID3 tags parser" ) ); 
     53    set_description( _("ID3v1/2 and APEv1/2 tags parser" ) ); 
    5454    set_capability( "meta reader", 70 ); 
    55     set_callbacks( ParseID3Tags, NULL ); 
     55    set_callbacks( ParseTags, NULL ); 
    5656vlc_module_end(); 
    57  
    58 /***************************************************************************** 
    59  * Definitions of structures  and functions used by this plugins 
    60  *****************************************************************************/ 
    6157 
    6258/***************************************************************************** 
     
    6763    struct id3_tag   *p_id3_tag; 
    6864    struct id3_frame *p_frame; 
    69     int i = 0; 
     65    vlc_meta_t *p_meta = (vlc_meta_t *)p_demux->p_private; 
     66    int i; 
    7067 
    7168    p_id3_tag = id3_tag_parse( p_data, i_size ); 
    72     if( !p_id3_tag ) return; 
    73  
    74     if( !p_demux->p_private ) p_demux->p_private = (void *)vlc_meta_New(); 
    75  
    76     vlc_meta_t *p_meta = (vlc_meta_t *)(p_demux->p_private); 
     69    if( !p_id3_tag ) 
     70        return; 
     71 
     72    if( !p_meta ) 
     73        p_demux->p_private = p_meta = vlc_meta_New(); 
     74 
    7775#define ID_IS( a ) (!strcmp(  p_frame->id, a )) 
    7876#define DESCR_IS( a) strstr( (char*)p_frame->description, a ) 
    79  
    80     while ( ( p_frame = id3_tag_findframe( p_id3_tag, "UFID", i ) ) ) 
    81     { 
    82         char *psz_owner = id3_field_getlatin1( &p_frame->fields[0] ); 
     77#define GET_STRING(frame,fidx) id3_ucs4_latin1duplicate( id3_field_getstring( &(frame)->fields[fidx] ) ) 
     78 
     79    /* */ 
     80    for( i = 0; (p_frame = id3_tag_findframe( p_id3_tag, "UFID", i )) != NULL; i++ ) 
     81    { 
     82        const char *psz_owner = id3_field_getlatin1( &p_frame->fields[0] ); 
    8383 
    8484        if( !strncmp( psz_owner, "http://musicbrainz.org", 22 ) ) 
     
    9696            free( psz_ufid ); 
    9797        } 
    98         i++; 
    99     } 
    100 #if 0 //not used 
    101     i = 0; 
    102  
    103     while( ( p_frame = id3_tag_findframe( p_id3_tag, "TXXX", i ) ) ) 
    104     { 
    105         char *psz_desc = id3_ucs4_latin1duplicate( 
    106                 id3_field_getstring( &p_frame->fields[1] ) ); 
    107  
    108         if ( ! strncmp( psz_desc, "MusicBrainz Artist Id", 21 ) ) 
     98    } 
     99 
     100    /* User defined text (TXXX) */ 
     101    for( i = 0; (p_frame = id3_tag_findframe( p_id3_tag, "TXXX", i )) != NULL; i++ ) 
     102    { 
     103        /* 3 fields: 'encoding', 'description', 'value' */ 
     104        char *psz_name = GET_STRING( p_frame, 1 ); 
     105        char *psz_value = GET_STRING( p_frame, 2 ); 
     106 
     107        vlc_meta_AddExtra( p_meta, psz_name, psz_value ); 
     108#if 0 
     109        if( !strncmp( psz_name, "MusicBrainz Artist Id", 21 ) ) 
     110            vlc_meta_SetArtistID( p_meta, psz_value ); 
     111        if( !strncmp( psz_desc, "MusicBrainz Album Id", 20 ) ) 
     112            vlc_meta_SetAlbumID( p_meta, psz_value ); 
     113#endif 
     114        free( psz_name ); 
     115        free( psz_value ); 
     116    } 
     117 
     118    /* Relative volume adjustment */ 
     119    for( i = 0; (p_frame = id3_tag_findframe( p_id3_tag, "RVA2", i )) != NULL; i++ ) 
     120    { 
     121        /* 2 fields: 'latin1', 'binary' */ 
     122        const char *psz_type = id3_field_getlatin1( &p_frame->fields[0] ); 
     123        if( !strcasecmp( psz_type, "track" ) || !strcasecmp( psz_type, "album" ) || 
     124            !strcasecmp( psz_type, "normalize" ) ) 
    109125        { 
    110             char *psz_artistid = id3_ucs4_latin1duplicate( 
    111                     id3_field_getstring( &p_frame->fields[2] ) ); 
    112             vlc_meta_SetArtistID( p_meta, psz_artistid ); 
    113             free( psz_artistid ); 
     126            id3_byte_t const * p_data; 
     127            id3_length_t i_data; 
     128 
     129            p_data = id3_field_getbinarydata( &p_frame->fields[1], &i_data ); 
     130            while( i_data >= 4 ) 
     131            { 
     132                const unsigned int i_peak_size = p_data[3]; 
     133                const float f_gain = (float)GetWBE( &p_data[1] ) / 512.0; 
     134                char psz_value[32]; 
     135 
     136                if( i_data < i_peak_size + 4 ) 
     137                    break; 
     138                /* only master volume */ 
     139                if( p_data[0] == 0x01 ) 
     140                { 
     141                    snprintf( psz_value, sizeof(psz_value), "%f", f_gain ); 
     142                    if( !strcasecmp( psz_type, "album" ) ) 
     143                        vlc_meta_AddExtra( p_meta, "REPLAYGAIN_ALBUM_GAIN", psz_value ); 
     144                    else 
     145                        vlc_meta_AddExtra( p_meta, "REPLAYGAIN_TRACK_GAIN", psz_value ); 
     146                    /* XXX I have no idea what peak unit is ... */ 
     147                } 
     148                i_data -= 4+i_peak_size; 
     149            } 
    114150        } 
    115  
    116         if ( ! strncmp( psz_desc, "MusicBrainz Album Id", 20 ) ) 
    117         { 
    118             char *psz_albumid = id3_ucs4_latin1duplicate( 
    119                     id3_field_getstring( &p_frame->fields[2] ) ); 
    120             vlc_meta_SetAlbumID( p_meta, psz_albumid ); 
    121             free( psz_albumid ); 
    122         } 
    123  
    124         free( psz_desc ); 
    125         i++; 
    126     } 
    127 #endif 
    128     i = 0; 
    129  
    130     while( ( p_frame = id3_tag_findframe( p_id3_tag , "T", i ) ) ) 
    131     { 
    132         int i_strings = id3_field_getnstrings( &p_frame->fields[1] ); 
    133  
     151    } 
     152 
     153    /* TODO 'RGAD' if it is used somewhere */ 
     154 
     155    /* T--- Text informations */ 
     156    for( i = 0; (p_frame = id3_tag_findframe( p_id3_tag, "T", i )) != NULL; i++ ) 
     157    { 
     158        int i_strings; 
     159         
     160        /* Special case TXXX is not the same beast */ 
     161        if( ID_IS( "TXXX" ) ) 
     162            continue; 
     163 
     164        i_strings = id3_field_getnstrings( &p_frame->fields[1] ); 
    134165        while( i_strings > 0 ) 
    135166        { 
     
    194225            } 
    195226            else if( p_frame->description ) 
    196             { /* Unhandled meta*/ 
    197                 msg_Warn( p_demux, "Fixme: unhandled ID3 metatag, %s", p_frame->description ); 
     227            { 
     228                /* Unhandled meta */ 
     229                vlc_meta_AddExtra( p_meta, (char*)p_frame->description, psz_temp ); 
    198230            } 
    199231            free( psz_temp ); 
    200232        } 
    201         i++; 
    202233    } 
    203234    id3_tag_delete( p_id3_tag ); 
     235#undef GET_STRING 
     236#undef DESCR_IS 
     237#undef ID_IS 
    204238} 
    205  
    206 /***************************************************************************** 
    207  * ParseID3Tags: check if ID3 tags at common locations. Parse them and skip it 
    208  * if it's at the start of the file 
     239/***************************************************************************** 
     240 * APEv1/2 
     241 *****************************************************************************/ 
     242#define APE_TAG_HEADERSIZE (32) 
     243static int GetAPEvXSize( const uint8_t *p_data, int i_data ) 
     244
     245    uint32_t flags; 
     246    int i_body; 
     247 
     248    if( i_data < APE_TAG_HEADERSIZE || 
     249        ( GetDWLE( &p_data[8] ) != 1000 && GetDWLE( &p_data[8] ) != 2000 ) || /* v1/v2 only */ 
     250        strncmp( (char*)p_data, "APETAGEX", 8 ) || 
     251        GetDWLE( &p_data[8+4+4] ) <= 0 ) 
     252        return 0; 
     253 
     254    i_body = GetDWLE( &p_data[8+4] ); 
     255    flags = GetDWLE( &p_data[8+4+4] ); 
     256 
     257    /* is it the header */ 
     258    if( flags & (1<<29) ) 
     259        return i_body + ( (flags&(1<<30)) ? APE_TAG_HEADERSIZE : 0 ); 
     260 
     261    /* it is the footer */ 
     262    return i_body + ( (flags&(1<<31)) ? APE_TAG_HEADERSIZE : 0 ); 
     263
     264static void ParseAPEvXTag( demux_t *p_demux, uint8_t *p_data, int i_data ) 
     265
     266    vlc_meta_t *p_meta = (vlc_meta_t *)p_demux->p_private; 
     267    vlc_bool_t b_start; 
     268    vlc_bool_t b_end; 
     269    uint8_t *p_header = NULL; 
     270    int i_entry; 
     271 
     272    if( i_data < APE_TAG_HEADERSIZE ) 
     273        return; 
     274 
     275    b_start = !strncmp( (char*)&p_data[0], "APETAGEX", 8 ); 
     276    b_end = !strncmp( (char*)&p_data[i_data-APE_TAG_HEADERSIZE], "APETAGEX", 8 ); 
     277    if( !b_end && !b_start ) 
     278        return; 
     279 
     280    if( b_start ) 
     281    { 
     282        p_header = &p_data[0]; 
     283        p_data += APE_TAG_HEADERSIZE; 
     284        i_data -= APE_TAG_HEADERSIZE; 
     285    } 
     286    if( b_end ) 
     287    { 
     288        p_header = &p_data[i_data-APE_TAG_HEADERSIZE]; 
     289        i_data -= APE_TAG_HEADERSIZE; 
     290    } 
     291    if( i_data <= 0 ) 
     292        return; 
     293 
     294    i_entry = GetDWLE( &p_header[8+4+4] ); 
     295    if( i_entry <= 0 ) 
     296        return; 
     297 
     298    if( !p_meta ) 
     299        p_demux->p_private = p_meta = vlc_meta_New(); 
     300 
     301    while( i_entry > 0 && i_data >= 10 ) 
     302    { 
     303        const int i_size = GetDWLE( &p_data[0] ); 
     304        const uint32_t flags = GetDWLE( &p_data[4] ); 
     305        char psz_name[256]; 
     306        int n; 
     307 
     308        strlcpy( psz_name, (char*)&p_data[8], sizeof(psz_name) ); 
     309        n = strlen( psz_name ); 
     310        if( n <= 0 ) 
     311            break; 
     312 
     313        p_data += 8+n+1; 
     314        i_data -= 8+n+1; 
     315        if( i_data < i_size ) 
     316            break; 
     317 
     318        /* Retreive UTF-8 fields only */ 
     319        if( ((flags>>1) & 0x03) == 0x00 ) 
     320        { 
     321            /* FIXME list are separated by '\0' */ 
     322            char *psz_value = strndup( (char*)&p_data[0], i_size ); 
     323 
     324            EnsureUTF8( psz_name ); 
     325            EnsureUTF8( psz_value ); 
     326#define IS(s) (!strcasecmp( psz_name, s ) ) 
     327            if( IS( "Title" ) ) 
     328                vlc_meta_SetTitle( p_meta, psz_value ); 
     329            else  if( IS( "Artist" ) ) 
     330                vlc_meta_SetArtist( p_meta, psz_value ); 
     331            else  if( IS( "Album" ) ) 
     332                vlc_meta_SetAlbum( p_meta, psz_value ); 
     333            else  if( IS( "Publisher" ) ) 
     334                vlc_meta_SetPublisher( p_meta, psz_value ); 
     335            else  if( IS( "Track" ) ) 
     336            { 
     337                char *p = strchr( psz_value, '/' ); 
     338                if( p ) 
     339                    *p++ = '\0'; 
     340                vlc_meta_SetTracknum( p_meta, psz_value ); 
     341            } 
     342            else  if( IS( "Comment" ) ) 
     343                vlc_meta_SetDescription( p_meta, psz_value ); 
     344            else  if( IS( "Copyright" ) ) 
     345                vlc_meta_SetCopyright( p_meta, psz_value ); 
     346            else  if( IS( "Year" ) ) 
     347                vlc_meta_SetDate( p_meta, psz_value ); 
     348            else  if( IS( "Genre" ) ) 
     349                vlc_meta_SetGenre( p_meta, psz_value ); 
     350            else  if( IS( "Language" ) ) 
     351                vlc_meta_SetLanguage( p_meta, psz_value ); 
     352            else 
     353                vlc_meta_AddExtra( p_meta, psz_name, psz_value ); 
     354#undef IS 
     355            free( psz_value ); 
     356        } 
     357 
     358        p_data += i_size; 
     359        i_data -= i_size; 
     360        i_entry--; 
     361    } 
     362
     363 
     364/***************************************************************************** 
     365 * CheckFooter: check for ID3/APE at the end of the file 
     366 * CheckHeader: check for ID3/APE at the begining of the file 
     367 *****************************************************************************/ 
     368static void CheckFooter( demux_t *p_demux ) 
     369
     370    const int64_t i_pos = stream_Size( p_demux->s ); 
     371    const int i_peek = 128+APE_TAG_HEADERSIZE; 
     372    uint8_t *p_peek; 
     373    uint8_t *p_peek_id3; 
     374    int64_t i_id3v2_pos = -1; 
     375    int64_t i_apevx_pos = -1; 
     376    int i_id3v2_size; 
     377    int i_apevx_size; 
     378    int i_id3v1_size; 
     379 
     380    if( i_pos < i_peek ) 
     381        return; 
     382    if( stream_Seek( p_demux->s, i_pos - i_peek ) ) 
     383        return; 
     384 
     385    if( stream_Peek( p_demux->s, &p_peek, i_peek ) < i_peek ) 
     386        return; 
     387    p_peek_id3 = &p_peek[APE_TAG_HEADERSIZE]; 
     388 
     389    /* Check/Parse ID3v1 */ 
     390    i_id3v1_size = id3_tag_query( p_peek_id3, ID3_TAG_QUERYSIZE ); 
     391    if( i_id3v1_size == 128 ) 
     392    { 
     393        msg_Dbg( p_demux, "found ID3v1 tag" ); 
     394        ParseID3Tag( p_demux, p_peek_id3, i_id3v1_size ); 
     395    } 
     396 
     397    /* Compute ID3v2 position */ 
     398    i_id3v2_size = -id3_tag_query( &p_peek_id3[128-ID3_TAG_QUERYSIZE], ID3_TAG_QUERYSIZE ); 
     399    if( i_id3v2_size > 0 ) 
     400        i_id3v2_pos = i_pos - i_id3v2_size; 
     401 
     402    /* Compute APE2v2 position */ 
     403    i_apevx_size = GetAPEvXSize( &p_peek[128+0], APE_TAG_HEADERSIZE ); 
     404    if( i_apevx_size > 0 ) 
     405    { 
     406        i_apevx_pos = i_pos - i_apevx_size; 
     407    } 
     408    else if( i_id3v1_size > 0 ) 
     409    { 
     410        /* it can be before ID3v1 */ 
     411        i_apevx_size = GetAPEvXSize( p_peek, APE_TAG_HEADERSIZE ); 
     412        if( i_apevx_size > 0 ) 
     413            i_apevx_pos = i_pos - 128 - i_apevx_size; 
     414    } 
     415 
     416    if( i_id3v2_pos > 0 && i_apevx_pos > 0 ) 
     417    { 
     418        msg_Warn( p_demux, 
     419                  "Both ID3v2 and APEv1/2 at the end of file, ignoring APEv1/2" ); 
     420        i_apevx_pos = -1; 
     421    } 
     422 
     423    /* Parse ID3v2.4 */ 
     424    if( i_id3v2_pos > 0 ) 
     425    { 
     426        if( !stream_Seek( p_demux->s, i_id3v2_pos ) && 
     427            stream_Peek( p_demux->s, &p_peek, i_id3v2_size ) == i_id3v2_size ) 
     428        { 
     429            msg_Dbg( p_demux, "found ID3v2 tag at end of file" ); 
     430            ParseID3Tag( p_demux, p_peek, i_id3v2_size ); 
     431        } 
     432    } 
     433 
     434    /* Parse APEv1/2 */ 
     435    if( i_apevx_pos > 0 ) 
     436    { 
     437        if( !stream_Seek( p_demux->s, i_apevx_pos ) && 
     438            stream_Peek( p_demux->s, &p_peek, i_apevx_size ) == i_apevx_size ) 
     439        { 
     440            msg_Dbg( p_demux, "found APEvx tag at end of file" ); 
     441            ParseAPEvXTag( p_demux, p_peek, i_apevx_size ); 
     442        } 
     443    } 
     444
     445static void CheckHeader( demux_t *p_demux ) 
     446
     447    uint8_t *p_peek; 
     448    int i_size; 
     449 
     450    if( stream_Seek( p_demux->s, 0 ) ) 
     451        return; 
     452 
     453    /* Test ID3v2 first */ 
     454    if( stream_Peek( p_demux->s, &p_peek, ID3_TAG_QUERYSIZE ) != ID3_TAG_QUERYSIZE ) 
     455        return; 
     456    i_size = id3_tag_query( p_peek, ID3_TAG_QUERYSIZE ); 
     457    if( i_size > 0 && 
     458        stream_Peek( p_demux->s, &p_peek, i_size ) == i_size ) 
     459    { 
     460        msg_Dbg( p_demux, "found ID3v2 tag" ); 
     461        ParseID3Tag( p_demux, p_peek, i_size ); 
     462        return; 
     463    } 
     464 
     465    /* Test APEv1 */ 
     466    if( stream_Peek( p_demux->s, &p_peek, APE_TAG_HEADERSIZE ) != APE_TAG_HEADERSIZE ) 
     467        return; 
     468    i_size = GetAPEvXSize( p_peek, APE_TAG_HEADERSIZE ); 
     469    if( i_size > 0 && 
     470        stream_Peek( p_demux->s, &p_peek, i_size ) == i_size ) 
     471    { 
     472        msg_Dbg( p_demux, "found APEv1/2 tag" ); 
     473        ParseAPEvXTag( p_demux, p_peek, i_size ); 
     474    } 
     475
     476 
     477/***************************************************************************** 
     478 * ParseTags: check if ID3/APE tags at common locations. 
    209479 ****************************************************************************/ 
    210 static int ParseID3Tags( vlc_object_t *p_this ) 
     480static int ParseTags( vlc_object_t *p_this ) 
    211481{ 
    212482    demux_t *p_demux = (demux_t *)p_this; 
    213     uint8_t *p_peek; 
    214483    vlc_bool_t b_seekable; 
    215     int64_t i_init, i_pos; 
    216     int i_size; 
     484    int64_t i_init; 
    217485 
    218486    p_demux->p_private = NULL; 
    219487 
    220     msg_Dbg( p_demux, "checking for ID3 tag" ); 
     488    msg_Dbg( p_demux, "checking for ID3v1/2 and APEv1/2 tags" ); 
    221489 
    222490    stream_Control( p_demux->s, STREAM_CAN_FASTSEEK, &b_seekable ); 
    223     if( !b_seekable ) return VLC_SUCCESS; 
     491    if( !b_seekable ) 
     492        return VLC_SUCCESS; 
    224493 
    225494    i_init = stream_Tell( p_demux->s ); 
    226495 
    227     /* 
    228      * Look for a ID3v1 tag at the end of the file 
     496    /* */ 
     497    CheckFooter( p_demux ); 
     498 
     499    /* */ 
     500    CheckHeader( p_demux ); 
     501 
     502    /* Restore position 
     503     *  Demuxer will not see tags at the start as src/input/demux.c skips it 
     504     *  for them 
    229505     */ 
    230     i_init = stream_Tell( p_demux->s ); 
    231     i_pos = stream_Size( p_demux->s ); 
    232  
    233     while( i_pos > 128 ) /* while used so we can break; */ 
    234     { 
    235         stream_Seek( p_demux->s, i_pos - 128 ); 
    236  
    237         /* get 10 byte id3 header */ 
    238         if( stream_Peek( p_demux->s, &p_peek, 10 ) < 10 ) break; 
    239  
    240         i_size = id3_tag_query( p_peek, 10 ); 
    241         if( i_size == 128 ) 
    242         { 
    243             /* peek the entire tag */ 
    244             if( stream_Peek( p_demux->s, &p_peek, i_size ) < i_size ) break; 
    245  
    246             msg_Dbg( p_demux, "found ID3v1 tag" ); 
    247             ParseID3Tag( p_demux, p_peek, i_size ); 
    248         } 
    249  
    250         /* look for ID3v2.4 tag at end of file */ 
    251         /* get 10 byte ID3 footer */ 
    252         if( stream_Peek( p_demux->s, &p_peek, 128 ) < 128 ) break; 
    253  
    254         i_size = id3_tag_query( p_peek + 118, 10 ); 
    255         if( i_size < 0  && i_pos > -i_size ) 
    256         { 
    257             /* id3v2.4 footer found */ 
    258             stream_Seek( p_demux->s , i_pos + i_size ); 
    259             /* peek the entire tag */ 
    260             if( stream_Peek( p_demux->s, &p_peek, i_size ) < i_size ) break; 
    261  
    262             msg_Dbg( p_demux, "found ID3v2 tag at end of file" ); 
    263             ParseID3Tag( p_demux, p_peek, i_size ); 
    264         } 
    265         break; 
    266     } 
    267  
    268     /* 
    269      * Get 10 byte id3 header 
    270      */ 
    271     stream_Seek( p_demux->s, 0 ); 
    272     if( stream_Peek( p_demux->s, &p_peek, 10 ) < 10 ) goto end; 
    273  
    274     if( (i_size = id3_tag_query( p_peek, 10 )) <= 0 ) goto end; 
    275  
    276     if( stream_Peek( p_demux->s, &p_peek, i_size ) < i_size ) goto end; 
    277  
    278     msg_Dbg( p_demux, "found ID3v2 tag" ); 
    279     ParseID3Tag( p_demux, p_peek, i_size ); 
    280  
    281  end: 
    282506    stream_Seek( p_demux->s, i_init ); 
    283507    return VLC_SUCCESS;