| 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" ) ) |
|---|
| 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) |
|---|
| | 243 | static 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 | } |
|---|
| | 264 | static 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 | *****************************************************************************/ |
|---|
| | 368 | static 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 | } |
|---|
| | 445 | static 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. |
|---|
| 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: |
|---|