17

My code:

- (void)metadata {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:self.fileURL options:nil];

NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtwork keySpace:AVMetadataKeySpaceCommon];
NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
NSArray *artists = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
NSArray *albumNames = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyAlbumName keySpace:AVMetadataKeySpaceCommon];

AVMetadataItem *artwork = [artworks objectAtIndex:0];
AVMetadataItem *title = [titles objectAtIndex:0];
AVMetadataItem *artist = [artists objectAtIndex:0];
AVMetadataItem *albumName = [albumNames objectAtIndex:0];

if ([artwork.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
    NSDictionary *dictionary = [artwork.value copyWithZone:nil];
    self.currentSongArtwork = [UIImage imageWithData:[dictionary objectForKey:@"data"]];
}
else if ([artwork.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
    self.currentSongArtwork = [UIImage imageWithData:[artwork.value copyWithZone:nil]];
}

self.currentSongTitle = [title.value copyWithZone:nil];
self.currentSongArtist = [artist.value copyWithZone:nil];
self.currentSongAlbumName = [albumName.value copyWithZone:nil];
self.currentSongDuration = self.audioPlayer.duration;
}

This works for fetching artwork from m4a files, but doesn’t work for mp3 files. If the asset points to an mp3 file, artworks is empty. What am I doing wrong and how do I fix it?

duci9y
  • 4,128
  • 3
  • 26
  • 42

3 Answers3

17

I found that the artworks were being loaded asynchronously while the image was being set synchronously. I solved it this way:

- (void)metadata {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:self.fileURL options:nil];

NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
NSArray *artists = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
NSArray *albumNames = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyAlbumName keySpace:AVMetadataKeySpaceCommon];

AVMetadataItem *title = [titles objectAtIndex:0];
AVMetadataItem *artist = [artists objectAtIndex:0];
AVMetadataItem *albumName = [albumNames objectAtIndex:0];


NSArray *keys = [NSArray arrayWithObjects:@"commonMetadata", nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
    NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
                                                       withKey:AVMetadataCommonKeyArtwork
                                                      keySpace:AVMetadataKeySpaceCommon];

    for (AVMetadataItem *item in artworks) {
        if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
            NSDictionary *d = [item.value copyWithZone:nil];
            self.currentSongArtwork = [UIImage imageWithData:[d objectForKey:@"data"]];
        } else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
            self.currentSongArtwork = [UIImage imageWithData:[item.value copyWithZone:nil]];
        }
    }
}];


self.currentSongTitle = [title.value copyWithZone:nil];
self.currentSongArtist = [artist.value copyWithZone:nil];
self.currentSongAlbumName = [albumName.value copyWithZone:nil];
self.currentSongDuration = self.audioPlayer.duration;
}
duci9y
  • 4,128
  • 3
  • 26
  • 42
  • 1
    In the iOS simulator and on my iPad Mini I get a SIGABRT on `[d objectForKey:@"data"]`. Works on the iPhone 5, however. Any ideas? – Stan James Sep 10 '14 at 04:45
  • @StanJames The iOS simulator doesn't have a music library. Don't test the Media Player framework there. – duci9y Sep 10 '14 at 11:10
  • [@duci9y](http://stackoverflow.com/users/1593651/duci9y) Hmm, but I am able to read other ID3 tags such as title and artist. (I'm not using the iPhone's "official" music library, just mp3 files that are included in my bundle or loaded by user.) Does the artwork tag function use some different library? – Stan James Sep 10 '14 at 22:03
  • @StanJames Seems to be a problem specific to your situation. I suggest you ask a new question. I'd appreciate it if you post a link to your question here so that I can follow up too. – duci9y Sep 10 '14 at 22:05
  • Will do. I'll with the iPad crashing I'll have to get to the bottom of this before launch anyway! – Stan James Sep 10 '14 at 22:06
  • Posted it here: http://stackoverflow.com/questions/25778757/getting-mp3-artwork-crashes-on-ios-simulator-and-ipad-but-works-on-iphone – Stan James Sep 11 '14 at 03:48
  • see comment for IOS 8 Below – Ryan Heitner Sep 21 '14 at 12:01
13

I just found the answer to that here: How can I extract metadata from mp3 file in ios development and was now looking to see why that code doesn't work on an m4a file. I took a little bit from your code and present the following which seems to work for both m4a and mp3. Note that I left the code which works for mp3 as an open else clause without the qualifier - if anybody gains more experience with this they are welcome to refine!

 - (void)getMetaDataForSong:(MusicItem *)musicItem {
   AVAsset *assest;

   // filePath looks something like this: /var/mobile/Applications/741647B1-1341-4203-8CFA-9D0C555D670A/Library/Caches/All Summer Long.m4a

    NSURL *fileURL = [NSURL fileURLWithPath:musicItem.filePath];
    NSLog(@"%@", fileURL);
    assest = [AVURLAsset URLAssetWithURL:fileURL    options:nil];
    NSLog(@"%@", assest);

    for (NSString *format in [assest availableMetadataFormats]) {
      for (AVMetadataItem *item in [assest metadataForFormat:format]) {
        if ([[item commonKey] isEqualToString:@"title"]) {
            musicItem.strSongTitle = (NSString *)[item value];
        } 
        if ([[item commonKey] isEqualToString:@"artist"]) {
            musicItem.strArtistName = (NSString *)[item value];
        }
        if ([[item commonKey] isEqualToString:@"albumName"]) {
          musicItem.strAlbumName = (NSString *)[item value];
        }
        if ([[item commonKey] isEqualToString:@"artwork"]) {
          UIImage *img = nil;
          if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
            img = [UIImage imageWithData:[item.value copyWithZone:nil]];
          }
          else { // if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
            NSData *data = [(NSDictionary *)[item value] objectForKey:@"data"];
            img = [UIImage imageWithData:data]  ;
          }
          musicItem.imgArtwork = img;
        }
     }
  }
}
Community
  • 1
  • 1
Andy Weinstein
  • 2,639
  • 3
  • 21
  • 32
  • Though this prevented the code from crashing, the essence of my code and this code remains the same. I found out the problem and solved it differently. Check my answer for the solution. – duci9y Dec 27 '12 at 11:48
  • 1
    Terrific! Yours looks a lot more robust. If I learn any more I'll look forward to updating. Thanks. – Andy Weinstein Jan 02 '13 at 09:46
  • 6
    IOS 8 use if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { albumArtwork = [UIImage imageWithData:item.dataValue]; } else { NSDictionary *dict ; [item.value copyWithZone:nil]; albumArtwork = [UIImage imageWithData:[dict objectForKey:@"data"]]; } – Ryan Heitner Sep 21 '14 at 12:00
  • 1
    Crashes at this line NSData *data = [(NSDictionary *)[item value] objectForKey:@"data"]; – Ibrar Ahmed Feb 18 '15 at 09:07
1

As Ryan Heitner pointed out in a comment to Andy Weinstein, the way to retrieve the binary artwork data from the AVMetadataKeySpaceID3 has changed somewhere from IOS7 to IOS8. Casting blindly item to NSDictionary will crash in the ID3 case nowadays. To make code bullet proof just check dataValue, otherwise the Classes. In fact today item.value points to item.dataValue and has the NSData class.

- (UIImage*)imageFromItem:(AVMetadataItem*)item
{
  NSData* data=nil;
  if (item.dataValue!=nil) {
    data=item.dataValue;
  } else if ([item.value isKindOfClass:NSData.class]) { //never arrive here nowadays
    data=(NSData*)item.value;
  } else if ([item.value isKindOfClass:NSDictionary.class]) { //never arrive here nowadays...
    NSDictionary* dict=(NSDictionary*)item.value;
    data=dict[@"data"];
  }
  if (data==nil) {
    return nil;
  }
  return [UIImage imageWithData:data];
}
Leo
  • 925
  • 10
  • 24