reading MP3 ID tags

Create a new topic, post a question, or jump in and answer someone else's question about iPhone development.

reading MP3 ID tags

Postby curylo » Sat Mar 28, 2009 8:26 pm

I have about 280 1 minute mP3 segments I want to read the ID tags from.
I am using AVAudioPlayer,
I am trying to figure out if there is a way to read the ID3 tags. It might be a hidden feature used in the iPod of the iPhone, BUT it would be awesome if i could figure out how to access the title.

Anyone know anything? Point me to the right documentation on this?
curylo
 
Posts: 12
Joined: Fri Feb 27, 2009 2:08 pm

Re: reading MP3 ID tags

Postby gcole » Mon Mar 30, 2009 1:27 am

Well, I can tell you that I've grown to really dislike the Core Foundation API. ;)

As you've found, AVAudioPlayer makes it really easy to play a sound. Getting information about the sound, though, is a bit more sticky. You've probably noticed that AVAudioPlayer doesn't support this at all, so we have to look elsewhere.

The Audio Toolbox Framework Reference provides over 200 pages of Core Foundation-based API.

To recap (since we'll be using a little bit of that code), we can set up the AVAudioPlayer with:
Code: Select all
    //assume 'player' is an instance variable declared as AVAudioPlayer * player,
    //with typical @property and @synthesize already done.

    NSString * soundFilePath = [[NSBundle mainBundle] pathForResource:@"LadyOfRome" ofType:@"mp3"];
   
    // we get an autoreleased NSURL ptr because we're going to use it further down this routine as well,
    // and we don't want to forget the release
    NSURL * fileURL = [NSURL fileURLWithPath: soundFilePath];
   
    AVAudioPlayer * audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
   
    [self setPlayer: audioPlayer];
    [audioPlayer release];
   
    [player setDelegate: self];
    [player prepareToPlay];

So far, so good. Cocoa rocks. :)

Now we get to the CF-based code. It doesn't start off too badly.
Code: Select all
    AudioFileID fileID  = nil;
    OSStatus err        = noErr;
   
    err = AudioFileOpenURL( (CFURLRef) fileURL, kAudioFileReadPermission, 0, &fileID );
    if( err != noErr ) {
        NSLog( @"AudioFileOpenURL failed" );
    }

Some CF types are "toll-free bridged" to their Cocoa counterparts, meaning we can just cast one to use the other. This comes in really handy above, where we just cast our NSURL pointer to its CF counterpart.

That AudioFileID is going to be used for several other calls. All routines return an OSStatus code indicating whether or not an error was encountered. Let's get cracking with the ID3 info.

We get the raw ID3 data in two steps. First we tell it what we want, and we get back the amount of memory we need to allocate ourselves. Then we allocate the memory and ask for the actual data.
Code: Select all
    UInt32 id3DataSize  = 0;
    char * rawID3Tag    = NULL;

    err = AudioFileGetPropertyInfo( fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL );
    if( err != noErr ) {
        NSLog( @"AudioFileGetPropertyInfo failed for ID3 tag" );
    }
    NSLog( @"id3 data size is %d bytes", id3DataSize );

    rawID3Tag = (char *) malloc( id3DataSize );
    if( rawID3Tag == NULL ) {
        NSLog( @"could not allocate %d bytes of memory for ID3 tag", id3DataSize );
    }
   
    err = AudioFileGetProperty( fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag );
    if( err != noErr ) {
        NSLog( @"AudioFileGetProperty failed for ID3 tag" );
    }
    NSLog( @"read %d bytes of ID3 info", id3DataSize );

To prove to ourselves that we really do have the raw ID3 data, we can display the first 100 bytes of data, substituting a dot (.) for each unprintable character:
Code: Select all
    int ilim = 100;
    if( ilim > id3DataSize ) {
        ilim = id3DataSize;
    }
    for( int i=0; i < ilim; i++ ) {
        if( rawID3Tag[i] < 32 ) printf( "." );
        else printf( "%c", rawID3Tag[i] );
    }
    printf( "\n" );

Running this on my machine (and with my .mp3 :)) yields:
Code: Select all
id3 data size is 334444 bytes
read 334444 bytes of ID3 info
ID3.....4bTT2.. .Lady Of Rome, Sister Of Brazil.TP1....Al Di Meola.TAL....Elegant Gypsy.TRK....5/6.T

So far, so good. And then...failure.

AudioFormatGetProperty looks like it has a property which interprets the raw ID3 tag, but doggone if I couldn't get anything reasonable out of this routine. There are two ID3-related properties. One is supposed to return the ID3 tag size, and the other a dictionary with the values parsed out. Even just getting the tag size failed:
Code: Select all
    UInt32 id3TagSize = 0;
    UInt32 id3TagSizeLength = 0;
    err = AudioFormatGetProperty( kAudioFormatProperty_ID3TagSize,
                                 id3DataSize,
                                 rawID3Tag,
                                 &id3TagSizeLength,
                                 &id3TagSize
                                 );
    if( err != noErr ) {
        NSLog( @"AudioFormatGetProperty failed for ID3 tag size" );
        switch( err ) {
            case kAudioFormatUnspecifiedError:
                NSLog( @"err: audio format unspecified error" );
                break;
            case kAudioFormatUnsupportedPropertyError:
                NSLog( @"err: audio format unsupported property error" );
                break;
            case kAudioFormatBadPropertySizeError:
                NSLog( @"err: audio format bad property size error" );
                break;
            case kAudioFormatBadSpecifierSizeError:
                NSLog( @"err: audio format bad specifier size error" );
                break;
            case kAudioFormatUnsupportedDataFormatError:
                NSLog( @"err: audio format unsupported data format error" );
                break;
            case kAudioFormatUnknownFormatError:
                NSLog( @"err: audio format unknown format error" );
                break;
            default:
                NSLog( @"err: some other audio format error" );
                break;
        }
    }
    NSLog( @"id3 tag size is %d bytes", id3TagSize );

For the longest time, this returned the supposedly-impossible "some other audio format error". Now it returns "unsupported property error." As far as ID3-specific info is concerned, I don't know where to go from here.

However, all is not lost.

The Audio File Services which worked for us above can return its own property info dictionary:
Code: Select all
    CFDictionaryRef piDict = nil;
    UInt32 piDataSize   = sizeof( piDict );

    err = AudioFileGetProperty( fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict );
    if( err != noErr ) {
        NSLog( @"AudioFileGetProperty failed for property info dictionary" );
    }

We can dump this dictionary either with Cocoa (nice) or with CF (ugly, but shows the types):
Code: Select all
    NSLog( @"property info: %@", (NSDictionary*)piDict );
    CFShow( piDict );

For me, this shows:
Code: Select all
property info: {
    album = "Elegant Gypsy";
    "approximate duration in seconds" = 105;
    artist = "Al Di Meola";
    "encoding application" = "iTunes v4.1";
    genre = Jazz;
    title = "Lady Of Rome, Sister Of Brazil";
    "track number" = "5/6";
    year = 1977;
}

<CFDictionary 0x553e80 [0xa06f11a0]>{type = mutable, count = 8, capacity = 12, pairs = (
   1 : <CFString 0x349afea4 [0xa06f11a0]>{contents = "track number"} = <CFString 0x53c4f0 [0xa06f11a0]>{contents = "5/6"}
   5 : <CFString 0x349afeb4 [0xa06f11a0]>{contents = "year"} = <CFString 0x540ad0 [0xa06f11a0]>{contents = "1977"}
   8 : <CFString 0x349afe74 [0xa06f11a0]>{contents = "artist"} = <CFString 0x553980 [0xa06f11a0]>{contents = "Al Di Meola"}
   9 : <CFString 0x349aff14 [0xa06f11a0]>{contents = "genre"} = <CFString 0x554040 [0xa06f11a0]>{contents = "Jazz"}
   10 : <CFString 0x349afe64 [0xa06f11a0]>{contents = "album"} = <CFString 0x553fe0 [0xa06f11a0]>{contents = "Elegant Gypsy"}
   11 : <CFString 0x349afee4 [0xa06f11a0]>{contents = "title"} = <CFString 0x553b30 [0xa06f11a0]>{contents = "Lady Of Rome, Sister Of Brazil"}
   12 : <CFString 0x349aff04 [0xa06f11a0]>{contents = "encoding application"} = <CFString 0x5537a0 [0xa06f11a0]>{contents = "iTunes v4.1"}
   13 : <CFString 0x349b0274 [0xa06f11a0]>{contents = "approximate duration in seconds"} = <CFString 0x553db0 [0xa06f11a0]>{contents = "105"}
)}

Accessing the properties using the Apple-supplied keys (look for the kAFInfoDictionary prefix) it's a pain either way:
Code: Select all
    // Cocoa
    NSString * genre = [(NSDictionary*)piDict objectForKey:
                        [NSString stringWithUTF8String: kAFInfoDictionary_Genre]
                        ];

    // CF
    // it sure would've been nice if they had DOCUMENTED that CFSTR needed to be used
    NSString * genreString = (NSString *)CFDictionaryGetValue( piDict, CFSTR( kAFInfoDictionary_Genre ));

In the end, we need to release the property info dictionary, as well as to free the ID3 area we created.
Code: Select all
    CFRelease( piDict );
    free( rawID3Tag );

Anyway, that should get you started.

(Updated 3 April to address some grammatical errors.)
Last edited by gcole on Fri Apr 03, 2009 7:20 pm, edited 1 time in total.
gcole
 
Posts: 977
Joined: Tue Dec 23, 2008 7:21 pm

Re: reading MP3 ID tags

Postby curylo » Fri Apr 03, 2009 4:57 pm

awesome reply dude! thanks! that got me a long way into this :) and i can see the light!
curylo
 
Posts: 12
Joined: Fri Feb 27, 2009 2:08 pm

Re: reading MP3 ID tags

Postby iphone_iphone » Wed Sep 30, 2009 9:26 am

Thanks dude... Great post...
iphone_iphone
 

Re: reading MP3 ID tags

Postby xcabo » Tue Jan 26, 2010 4:49 am

Hello,

first I would like to thank you for this post, it works very fine for local mp3.
But wonder how does it work for a stream. I tried the same way, with
- AudioFileStreamOpen
and
- AudioFileStreamGetPropertyInfo

but the method 'AudioFileStreamGetPropertyInfo' return the following error code: kAudioFileStreamError_UnsupportedProperty
Code: Select all
      UInt32 id3DataSize  = 0;
      char * rawID3Tag    = NULL;
      
      err = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFilePropertyID3Tag,  &id3DataSize, NULL);


I have no problem to open and play the file. So the 'audioFileStream' must be correct.
Have you got an idea on what is wrong ?
xcabo
 
Posts: 1
Joined: Tue Jan 26, 2010 4:34 am

Re: reading MP3 ID tags

Postby unreal_wh » Sun Jun 20, 2010 10:33 pm

Hi,First Thanks for your post .
But I still have a issue.If I want to get the APIC data value in the ID3 tag,the kAudioFilePropertyInfoDictionary don't contain the key,so how to add the new key for APIC?
Or just should analyse " AudioFileGetProperty( fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag );" the id3DataSize? Because it contains the all ID3 Tag data.
unreal_wh
 
Posts: 1
Joined: Sun Jun 20, 2010 10:15 pm

Re: reading MP3 ID tags

Postby joetraff » Fri Jul 23, 2010 9:54 pm

gcole replied in an amazing way.
I appreciate his/her efforts.
Thanks
joetraff
 
Posts: 2
Joined: Fri Jul 23, 2010 9:15 am


Return to iPhone Development Questions...

Who is online

Users browsing this forum: Yahoo [Bot] and 6 guests