I made the custom player by using AVAudioPlayer. Now, I want to fetch the details of the audio file such as artist name,album name,etc which is added in the resource folder.
I see that the tag-reading code Wayne posts is being copy-pasted too much.
If you use kAudioFilePropertyInfoDictionary
, you don't need id3DataSize, don't need to read raw ID3Tag data and id3TagSizeLength id3TagSize are not used also. Just open the file and read the tag:
- (NSDictionary *)id3TagsForURL:(NSURL *)resourceUrl
{
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((CFURLRef)resourceUrl, kAudioFileReadPermission, 0, &fileID);
if (result != noErr) {
NSLog(@"Error reading tags: %li", result);
return nil;
}
CFDictionaryRef piDict = nil;
UInt32 piDataSize = sizeof(piDict);
result = AudioFileGetProperty(fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict);
if (result != noErr)
NSLog(@"Error reading tags. AudioFileGetProperty failed");
AudioFileClose(fileID);
NSDictionary *tagsDictionary = [NSDictionary dictionaryWithDictionary:(NSDictionary*)piDict];
CFRelease(piDict);
return tagsDictionary;
}
Dicionary keys are defined in AudioFile.h, starting with kAFInfoDictionary.
However, there is another tag-reading key, which will return different results (with other keys):
- (NSDictionary *)id3TagsForURL:(NSURL *)resourceUrl
{
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((CFURLRef)resourceUrl, kAudioFileReadPermission, 0, &fileID);
if (result != noErr) {
return nil;
}
//read raw ID3Tag size
UInt32 id3DataSize = 0;
char *rawID3Tag = NULL;
result = AudioFileGetPropertyInfo(fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL);
if (result != noErr) {
AudioFileClose(fileID);
return nil;
}
rawID3Tag = (char *)malloc(id3DataSize);
//read raw ID3Tag
result = AudioFileGetProperty(fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag);
if (result != noErr) {
free(rawID3Tag);
AudioFileClose(fileID);
return nil;
}
CFDictionaryRef piDict = nil;
UInt32 piDataSize = sizeof(piDict);
//this key returns some other dictionary, which works also in iPod library
result = AudioFormatGetProperty(kAudioFormatProperty_ID3TagToDictionary, id3DataSize, rawID3Tag, &piDataSize, &piDict);
if (result != noErr) {
return nil;
}
free(rawID3Tag);
AudioFileClose(fileID);
NSDictionary *tagsDictionary = [NSDictionary dictionaryWithDictionary:(NSDictionary*)piDict];
CFRelease(piDict);
return tagsDictionary;
}
You can get this information through the AudioToolbox.framework
. The AudioToolbox.framework
is a C API, so I wrote an Objective-C wrapper for it:
ID3Tag .h:
@interface ID3Tag : NSObject <NSCoding> {
NSString* title_;
NSString* album_;
NSString* artist_;
NSNumber* trackNumber_;
NSNumber* totalTracks_;
NSString* genre_;
NSString* year_;
NSNumber* approxDuration_;
NSString* composer_;
NSString* tempo_;
NSString* keySignature_;
NSString* timeSignature_;
NSString* lyricist_;
NSString* recordedDate_;
NSString* comments_;
NSString* copyright_;
NSString* sourceEncoder_;
NSString* encodingApplication_;
NSString* bitRate_;
NSStream* sourceBitRate_;
NSString* channelLayout_;
NSString* isrc_;
NSString* subtitle_;
}
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *album;
@property (nonatomic, retain) NSString *artist;
@property (nonatomic, retain) NSNumber *trackNumber;
@property (nonatomic, retain) NSNumber *totalTracks;
@property (nonatomic, retain) NSString *genre;
@property (nonatomic, retain) NSString *year;
@property (nonatomic, retain) NSNumber *approxDuration;
@property (nonatomic, retain) NSString *composer;
@property (nonatomic, retain) NSString *tempo;
@property (nonatomic, retain) NSString *keySignature;
@property (nonatomic, retain) NSString *timeSignature;
@property (nonatomic, retain) NSString *lyricist;
@property (nonatomic, retain) NSString *recordedDate;
@property (nonatomic, retain) NSString *comments;
@property (nonatomic, retain) NSString *copyright;
@property (nonatomic, retain) NSString *sourceEncoder;
@property (nonatomic, retain) NSString *encodingApplication;
@property (nonatomic, retain) NSString *bitRate;
@property (nonatomic, retain) NSStream *sourceBitRate;
@property (nonatomic, retain) NSString *channelLayout;
@property (nonatomic, retain) NSString *isrc;
@property (nonatomic, retain) NSString *subtitle;
@end
ID3TagParser.h
#import <Foundation/Foundation.h>
#import "ID3Tag.h"
@interface ID3Parser : NSObject {
}
- (ID3Tag*) parseAudioFileForID3Tag:(NSURL*) url;
@end
ID3TagParser.m
#import "ID3Parser.h"
#import <AudioToolbox/AudioToolbox.h>
@implementation ID3Parser
- (ID3Tag*) parseAudioFileForID3Tag:(NSURL*) url {
if (url == nil) {
return nil;
}
AudioFileID fileID = nil;
OSStatus err = noErr;
err = AudioFileOpenURL( (CFURLRef) url, kAudioFileReadPermission, 0, &fileID );
if( err != noErr ) {
NSLog( @"AudioFileOpenURL failed" );
return nil;
} else {
UInt32 id3DataSize = 0;
char* rawID3Tag = NULL;
// Reads in the raw ID3 tag info
err = AudioFileGetPropertyInfo(fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL);
if(err != noErr) {
return nil;
}
// Allocate the raw tag data
rawID3Tag = (char *) malloc(id3DataSize);
if(rawID3Tag == NULL) {
return nil;
}
err = AudioFileGetProperty(fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag);
if(err != noErr) {
return nil;
}
UInt32 id3TagSize = 0;
UInt32 id3TagSizeLength = 0;
err = AudioFormatGetProperty(kAudioFormatProperty_ID3TagSize, id3DataSize, rawID3Tag, &id3TagSizeLength, &id3TagSize);
if(err != noErr) {
switch(err) {
case kAudioFormatUnspecifiedError:
NSLog(@"err: audio format unspecified error");
return nil;
case kAudioFormatUnsupportedPropertyError:
NSLog(@"err: audio format unsupported property error");
return nil;
case kAudioFormatBadPropertySizeError:
NSLog(@"err: audio format bad property size error");
return nil;
case kAudioFormatBadSpecifierSizeError:
NSLog(@"err: audio format bad specifier size error");
return nil;
case kAudioFormatUnsupportedDataFormatError:
NSLog(@"err: audio format unsupported data format error");
return nil;
case kAudioFormatUnknownFormatError:
NSLog(@"err: audio format unknown format error");
return nil;
default:
NSLog(@"err: some other audio format error");
return nil;
}
}
CFDictionaryRef piDict = nil;
UInt32 piDataSize = sizeof(piDict);
// Populates a CFDictionary with the ID3 tag properties
err = AudioFileGetProperty(fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict);
if(err != noErr) {
NSLog(@"AudioFileGetProperty failed for property info dictionary");
return nil;
}
// Toll free bridge the CFDictionary so that we can interact with it via objc
NSDictionary* nsDict = (NSDictionary*)piDict;
ID3Tag* tag = [[[ID3Tag alloc] init] autorelease];
tag.album = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Album]];
tag.approxDuration = [NSNumber numberWithInt:[[nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ApproximateDurationInSeconds]] intValue]];
tag.artist = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Artist]];
tag.bitRate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_NominalBitRate]];
tag.channelLayout = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ChannelLayout]];
tag.comments = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Comments]];
tag.composer = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Composer]];
tag.copyright = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Copyright]];
tag.encodingApplication = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_EncodingApplication]];
tag.genre = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Genre]];
tag.isrc = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ISRC]];
tag.keySignature = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_KeySignature]];
tag.lyricist = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Lyricist]];
tag.recordedDate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_RecordedDate]];
tag.sourceBitRate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SourceBitDepth]];
tag.sourceEncoder = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SourceEncoder]];
tag.subtitle = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SubTitle]];
tag.tempo = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Tempo]];
tag.timeSignature = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_TimeSignature]];
tag.title = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Title]];
tag.year = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Year]];
/*
* We're going to parse tracks differently so that we can perform queries on the data. This means we need to look
* for a '/' so that we can seperate out the track from the total tracks on the source compilation (if it's there).
*/
NSString* tracks = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_TrackNumber]];
int slashLocation = [tracks rangeOfString:@"/"].location;
if (slashLocation == NSNotFound) {
tag.trackNumber = [NSNumber numberWithInt:[tracks intValue]];
} else {
tag.trackNumber = [NSNumber numberWithInt:[[tracks substringToIndex:slashLocation] intValue]];
tag.totalTracks = [NSNumber numberWithInt:[[tracks substringFromIndex:(slashLocation+1 < [tracks length] ? slashLocation+1 : 0 )] intValue]];
}
// ALWAYS CLEAN UP!
CFRelease(piDict);
nsDict = nil;
free(rawID3Tag);
return tag;
}
}
@end