Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS - Getting [Explicit] and [Clean] tags from Media in Objective-C

When I view my purchased music in iTunes songs that are explicit are shown with a red explicit box next to them in the list of songs. I.e. the explicit tag you see when purchasing the song is passed through to the iTunes library. Also the same is done for songs itunes tagged as clean. I'd really like to be able to identify any songs that are explicit in my ios app but I can't find any resources on whether the tag is passed through to the ios devices with the other song information (MPMediaItemProperty) and if so how to get it (I've searched endlessly!) Some songs include "[Explicit] at the end of their title, but this is only the case with some songs, not all. Does anyone know if the tag is present and readable from Objective-C code?

like image 645
Adam Avatar asked Nov 03 '12 10:11

Adam


1 Answers

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:

  1. I needed to get a hold of a MPMediaItem containing the file I want to inspect. I did this using MPMediaPickerController, 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:...];
        ....
    }
    
  2. Once I have my MPMediaItem, I need to get the equivalent AVAsset so I can work with the AVFoundation 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];
        ....
    
  3. 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];
    
  4. 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;
    }];
    
  5. 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.

like image 62
WDUK Avatar answered Nov 14 '22 22:11

WDUK