Well, this took me a good few hours to find! Be prepared, it's not as simple as just using MPMediaQuery
.
Overview
The data you need isn't easily accessible via the standard channels (i.e There is no pre-defined key in the MediaPlayer or AVFoundation frameworks to access this metadata). It is available via most ID3 tag editors, under the name ITUNESADVISORY
. This tag can take 3 values:
0 - No advisory information
1 - Explicit
2 - Clean
This is what we're looking for, but how do we access it. The MediaPlayer
framework is fairly high level, which means that we are limited in what data we can access/use. In order to look more deeply at a media file, we need to use AVFoundation
framework as well.
How-to
I don't know what exact workflow you're using, but I hope you can adapt this to your needs. In order to determine whether a media file is explicit/clean or otherwise, I did as follows:
I needed to get a hold of a
MPMediaItem
containing the file I want to inspect. I did this usingMPMediaPickerController
, presenting it, and retrieved the selected media items through its delegate method:- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection { // Do whatever is appropriate in your case to extract the media items MPMediaItem* item = [mediaItemCollection.items objectAtIndex:...]; .... }
Once I have my
MPMediaItem
, I need to get the equivalentAVAsset
so I can work with theAVFoundation
framework. To do this, I can use the URL path from the media item to create the asset.MPMediaItem* item = ...; NSURL* path = [item valueForProperty:MPMediaItemPropertyAssetURL]; // We have to check if a path exists, because a media item may not be present on the device. Blame iTunes Match. if (path) { AVAsset* asset = [AVAsset assetWithURL:path]; ....
Once we have our asset, we need to get the metadata from it. From experimentation, this is iTunes metadata. As such, lets extract it from our asset.
NSArray* metadata = [asset metadataForFormat:AVMetadataFormatiTunesMetadata];
This is where the fun, undocumented work begins. We now have to find the correct metadata item for what we want. As the advisory key is not declared as a constant in any of Apple's documentation, after a lot of trial and error, I've found it to be
1920233063
.To get the correct metadata item, we need to perform a filter on the metadata array, as so:
NSUInteger advisoryIndex = [metadata indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[AVMetadataItem class]]) { AVMetadataItem* metaItem = (AVMetadataItem*)obj; NSNumber* advisoryKeyTest = @(1920233063); if ([metaItem.key isEqual:advisoryKeyTest]) { *stop = YES; return YES; } return NO; } return NO; }];
Once we've done that, then we need to determine what it is. This is relatively simple, and can be adapted to your own needs. I've just written a simple switch statement that says what type the song/media is. (Remember: It's entirely possible the media file doesn't include an advisory tag, so we have to check whether the index exists or not)
if (advisoryIndex != NSNotFound) { AVMetadataItem* metaItem = [metadata objectAtIndex:advisoryIndex]; NSInteger advisoryValue = [metaItem.numberValue integerValue]; switch (advisoryValue) { case 0: NSLog(@"%@", @"Unspecified"); break; case 1: NSLog(@"%@", @"Explicit"); break; case 2: NSLog(@"%@", @"Clean"); break; default: NSLog(@"%@", @"Unexpected Value"); break; } }
Conclusion
And there you have it, a way to determine whether a song is clean or explicit, or neither. Because this is using a static number to find the advisory data, and is not linked to any constant defined in Apple's headers, I can not guarantee that this will work for all iOS versions in the past, present and future. I have tested this on iOS 6, and it seemed to be ok. Your milage may vary.
It's a shame Apple don't provide this functionality out of the box (even if they just provided the Advisory Key). This functionality could be placed within a Category, if you so desire.