当前位置: 移动技术网 > IT编程>移动开发>IOS > iOS音频开发之资源(AVAsset)与元数据,解决获取资源属性问题(三)

iOS音频开发之资源(AVAsset)与元数据,解决获取资源属性问题(三)

2018年11月25日  | 移动技术网IT编程  | 我要评论

上海电影院,王子的新衣 萧敬腾,杨鲁豫父亲

本文主要是的分析,程序成功解决获取媒体元数据的信息,还可以对除了mp3之外的所有资源写入元数据信息。再次回顾我们的问题。

先提出一个问题,生活中有很多的媒体格式,mp3,avi,rmvb等等,在苹果环境下主要的媒体格式有4种,quicktime(mov),mpeg-4 video(mp4,m4v),mpeg-4 audio(m4a),mpeg-layer iii audio(mp3),那么问题来了,假如给你一个mp3文件,比如歌曲《再见.mp3》张震岳,你是不是有办法读取里面的数据,比如读取它的歌名,演唱者,属于哪个专辑,专辑的封面,文件的长度等等信息???

这是个mac程序,用户ui并不是我要讨论的重点。下面的代码,可以获取资源的路径,从而开始我们分析元数据。

 

- (void)loadtable {
    nsfilemanager *filemanager = [nsfilemanager defaultmanager];
    nsurl *rooturl = [nsurl fileurlwithpath:[filemanager applicationsupportdirectory]];
    nsarray *items = [filemanager contentsofdirectoryaturl:rooturl
                                includingpropertiesforkeys:@[nsurlnamekey, nsurleffectiveiconkey]
                                                   options:nsdirectoryenumerationskipshiddenfiles
                                                     error:nil];
    [items enumerateobjectsusingblock:^(nsurl *url, nsuinteger idx, bool *stop) {
        [self.mediaitemscontroller addobject:[[thmediaitem alloc] initwithurl:url]];
    }];
    [self.tableview reloaddata];
}
我们查看文件的组织结构,如下图,这里引出了thmediaitem类,我们的程序主要围绕这个类进行展开的。注意文件夹converters。

 

\
分析头文件,几个接口函数及几个属性函数。

 

typedef void(^thcompletionhandler)(bool complete);

@interface thmediaitem : nsobject

@property (strong, readonly) nsstring *filename;
@property (strong, readonly) nsstring *filetype;
@property (strong, readonly) thmetadata *metadata;
@property (readonly, getter = iseditable) bool editable;

- (id)initwithurl:(nsurl *)url;

- (void)preparewithcompletionhandler:(thcompletionhandler)handler;

- (void)savewithcompletionhandler:(thcompletionhandler)handler;

@end

 

需要注意类thmetadata。editable 表示是否可以编辑,mp3格式是无法编辑的,所以要区别对待,因为mp3有专利上的问题,所以苹果不支持。前面说过,不同的媒体的元数据格式是不同的,所以有个filetype属性,下面代码能解决这个这个问题,有m4a,m4v,mov,mp4,这些格式。

- (nsstring *)filetypeforurl:(nsurl *)url {
    nsstring *ext = [[self.url lastpathcomponent] pathextension];
    nsstring *type = nil;
    if ([ext isequaltostring:@"m4a"]) {
        type = avfiletypeapplem4a;
    } else if ([ext isequaltostring:@"m4v"]) {
        type = avfiletypeapplem4v;
    } else if ([ext isequaltostring:@"mov"]) {
        type = avfiletypequicktimemovie;
    } else if ([ext isequaltostring:@"mp4"]) {
        type = avfiletypempeg4;
    } else {
        type = avfiletypempeglayer3;
    }
    return type;
}

获取元数据的信息主要是在函数

preparewithcompletionhandler:(thcompletionhandler)completionhandler

中实现,实现的代码如下

 

- (void)preparewithcompletionhandler:(thcompletionhandler)completionhandler {

    if (self.prepared) {                                                    // 1
        completionhandler(self.prepared);
        return;
    }

    self.metadata = [[thmetadata alloc] init];                              // 2

    nsarray *keys = @[common_meta_key, available_meta_key];

    [self.asset loadvaluesasynchronouslyforkeys:keys completionhandler:^{

        avkeyvaluestatus commonstatus =
            [self.asset statusofvalueforkey:common_meta_key error:nil];

        avkeyvaluestatus formatsstatus =
            [self.asset statusofvalueforkey:available_meta_key error:nil];

        self.prepared = (commonstatus == avkeyvaluestatusloaded) &&         // 3
                        (formatsstatus == avkeyvaluestatusloaded);

        if (self.prepared) {
            for (avmetadataitem *item in self.asset.commonmetadata) {       // 4
                nslog(@"%@: %@  item commonkey : %@", item.keystring, item.value,item.commonkey);
                [self.metadata addmetadataitem:item withkey:item.commonkey];
            }

            for (id format in self.asset.availablemetadataformats) {        // 5
                if ([self.acceptedformats containsobject:format]) {
                    nsarray *items = [self.asset metadataforformat:format];
                    for (avmetadataitem *item in items) {
                         nslog(@"%@: %@", item.keystring, item.value);
                        [self.metadata addmetadataitem:item
                                               withkey:item.keystring];
                    }
                }
            }
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            completionhandler(self.prepared);
        });

    }];
}
其中common_meta_key,available_meta_key是定义的宏,这个函数在上一篇文章中介绍过。下面是一些打印信息

 

 

2016-09-18 16:20:33.239 metamanager[2582:194668] tit2: 再见 item commonkey : title

2016-09-18 16:20:33.239 metamanager[2582:194668] the keymapping {

"@cmt" = comments;

"@gen" = genre;

"@grp" = grouping;

"@wrt" = composer;

com = comments;

comm = comments;

tbp = bpm;

tbpm = bpm;

tdrc = year;

tp2 = albumartist;

tpa = discnumber;

tpe2 = albumartist;

tpos = discnumber;

trck = tracknumber;

trk = tracknumber;

tye = year;

tyer = year;

aart = albumartist;

albumname = album;

artist = artist;

artwork = artwork;

"com.apple.quicktime.director" = composer;

"com.apple.quicktime.genre" = genre;

"com.apple.quicktime.producer" = artist;

"com.apple.quicktime.year" = year;

creationdate = year;

creator = composer;

description = comments;

disk = discnumber;

grup = grouping;

ldes = comments;

subject = grouping;

title = name;

tmpo = bpm;

trkn = tracknumber;

type = genre;

}

2016-09-18 16:20:33.239 metamanager[2582:194668] talb: ok item commonkey : albumname2016-09-18 16:20:33.240 metamanager[2582:194668] tpe1: 张震岳 item commonkey : artist2016-09-18 16:20:33.246 metamanager[2582:194668] apic:

0 fffe003c 43524541 544f523a 2067642d 6a706567 2076312e 30202875 73696e67 20494a47 204a5045 47207636 32292c20 7175616c 69747920 3d203130 300affdb 00430001 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 010101ff db004301 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 ffc00011 0800f000 f0030122 00021101 031101ff c4001f00 00000603 01010100 00000000 00000000 04050708 090a0306 0b020100 ffc40041 10000201 03040004 04040502 050303.......

是不是我们找到我们想到的信息,比如歌手,标题名字等等。现在要将目光转移到thmetadata类,她的主要作用是从相关的元数据元素中提取值,并将她们保存,现在我们应该猜到这个类里面有很多的属性了吧。请看下面的thmetadata类头文件文件的内容。

 

@class thgenre;

@interface thmetadata : nsobject

@property (copy) nsstring *name;
@property (copy) nsstring *artist;
@property (copy) nsstring *albumartist;
@property (copy) nsstring *album;
@property (copy) nsstring *grouping;
@property (copy) nsstring *composer; //作曲家
@property (copy) nsstring *comments;
@property (strong) nsimage *artwork;//图片
@property (strong) thgenre *genre;

@property nsstring *year;
@property nsnumber *bpm;
@property nsnumber *tracknumber;
@property nsnumber *trackcount;
@property nsnumber *discnumber;
@property nsnumber *disccount;

- (void)addmetadataitem:(avmetadataitem *)item withkey:(id)key;

/**
 *  获取所有当前保存展示内容,并将转化类转化为avmetadataitem
 *
 *  @return 
 */
- (nsarray *)metadataitems;

@end

 

注意两个接口函数以及类thgenre。我们看看这个类的实现,首先是init函数

 

- (id)init {
    self = [super init];
    if (self) {
        _keymapping = [self buildkeymapping];                               // 1
        _metadata = [nsmutabledictionary dictionary];                       // 2
        _converterfactory = [[thmetadataconverterfactory alloc] init];      // 3
    }
    return self;
}

引出了两个重要的属性,keymapping 和 converterfactory。还记得前面提到文件夹converterfactory?这些文件都是为这个属性服务的,她的主要作用是将元数据的值通过提取和转换为可以用于展示的格式,比如nsstring。要实现对不同的键空间的键和格式的标准化工作,需要创建一个从指定格式键到标准化键的映射。所以有了keymapping,我们回头看看前面打印的信息,是不是有个keymapping,这个就是我们建立的keymapping。建立keymapping代码如下。

 

- (nsdictionary *)buildkeymapping {

    return @{
        // name mapping
        avmetadatacommonkeytitle : thmetadatakeyname,

        // artist mapping
        avmetadatacommonkeyartist : thmetadatakeyartist,
        avmetadataquicktimemetadatakeyproducer : thmetadatakeyartist,

        // album artist mapping
        avmetadataid3metadatakeyband : thmetadatakeyalbumartist,
        avmetadataitunesmetadatakeyalbumartist : thmetadatakeyalbumartist,
        @"tp2" : thmetadatakeyalbumartist,

        // album mapping
        avmetadatacommonkeyalbumname : thmetadatakeyalbum,

        // artwork mapping
        avmetadatacommonkeyartwork : thmetadatakeyartwork,

        // year mapping
        avmetadatacommonkeycreationdate : thmetadatakeyyear,
        avmetadataid3metadatakeyyear : thmetadatakeyyear,
        @"tye" : thmetadatakeyyear,
        avmetadataquicktimemetadatakeyyear : thmetadatakeyyear,
        avmetadataid3metadatakeyrecordingtime : thmetadatakeyyear,

        // bpm mapping
        avmetadataitunesmetadatakeybeatspermin : thmetadatakeybpm,
        avmetadataid3metadatakeybeatsperminute : thmetadatakeybpm,
        @"tbp" : thmetadatakeybpm,

        // grouping mapping
        avmetadataitunesmetadatakeygrouping : thmetadatakeygrouping,
        @"@grp" : thmetadatakeygrouping,
        avmetadatacommonkeysubject : thmetadatakeygrouping,

        // track number mapping
        avmetadataitunesmetadatakeytracknumber : thmetadatakeytracknumber,
        avmetadataid3metadatakeytracknumber : thmetadatakeytracknumber,
        @"trk" : thmetadatakeytracknumber,

        // composer mapping
        avmetadataquicktimemetadatakeydirector : thmetadatakeycomposer,
        avmetadataitunesmetadatakeycomposer : thmetadatakeycomposer,
        avmetadatacommonkeycreator : thmetadatakeycomposer,

        // disc number mapping
        avmetadataitunesmetadatakeydiscnumber : thmetadatakeydiscnumber,
        avmetadataid3metadatakeypartofaset : thmetadatakeydiscnumber,
        @"tpa" : thmetadatakeydiscnumber,

        // comments mapping
        @"ldes" : thmetadatakeycomments,
        avmetadatacommonkeydescription : thmetadatakeycomments,
        avmetadataitunesmetadatakeyusercomment : thmetadatakeycomments,
        avmetadataid3metadatakeycomments : thmetadatakeycomments,
        @"com" : thmetadatakeycomments,

        // genre mapping
        avmetadataquicktimemetadatakeygenre : thmetadatakeygenre,
        avmetadataitunesmetadatakeyusergenre : thmetadatakeygenre,
        avmetadatacommonkeytype : thmetadatakeygenre
    };
}
像thmetadatakeyname,thmetadatakeyartist这些常亮是在另外文件定义好的。接下来的函数的重点。

 

 

- (void)addmetadataitem:(avmetadataitem *)item withkey:(id)key {

    nslog(@"the keymapping %@",self.keymapping);  
    nsstring *normalizedkey = self.keymapping[key];                         // 1

    if (normalizedkey) {

        id  converter =                                // 2
            [self.converterfactory converterforkey:normalizedkey];

        // extract the value from the avmetadataitem instance and
        // convert it into a format suitable for presentation.
        id value = [converter displayvaluefrommetadataitem:item];

        // track and disc numbers/counts are stored as nsdictionary
        if ([value iskindofclass:[nsdictionary class]]) {                   // 3
            nsdictionary *data = (nsdictionary *) value;
            for (nsstring *currentkey in data) {
                if (![data[currentkey] iskindofclass:[nsnull class]]) {
                    [self setvalue:data[currentkey] forkey:currentkey];
                }
            }
        } else {
            //唱片和曲目编号是一种特殊的型号
            [self setvalue:value forkey:normalizedkey];
        }

        // store the avmetadataitem away for later use
        self.metadata[normalizedkey] = item;                                // 4
    }
}

 

这个函数传入两个函数 avmetadataitem数据,指的是item的commonkey,看一段打印信息

 

}, value=张震岳>,

这里commonkey指的是artist,定义的常量为thmetadatakeyartist。

现在注意这行代码

 

 id  converter =                                // 2
            [self.converterfactory converterforkey:normalizedkey];
终于联系到这个converterfactory函数了,这里有的协议protocol,thmetadataconverter,转换函数都是遵守这个协议的,请看下面代码寻找各自的类。

 

 

@implementation thmetadataconverterfactory

- (id )converterforkey:(nsstring *)key {

    id  converter = nil;

    if ([key isequaltostring:thmetadatakeyartwork]) {
        converter = [[thartworkmetadataconverter alloc] init];
    }
    else if ([key isequaltostring:thmetadatakeytracknumber]) {
        converter = [[thtrackmetadataconverter alloc] init];
    }
    else if ([key isequaltostring:thmetadatakeydiscnumber]) {
        converter = [[thdiscmetadataconverter alloc] init];
    }
    else if ([key isequaltostring:thmetadatakeycomments]) {
        converter = [[thcommentmetadataconverter alloc] init];
    }
    else if ([key isequaltostring:thmetadatakeygenre]) {
        converter = [[thgenremetadataconverter alloc] init];
    }
    else {
        converter = [[thdefaultmetadataconverter alloc] init];
    }

    return converter;
}
@end
请看下面的这个factory的类文件组织结构。

 

\

看有这么多的格式要判断。回到那个协议,请看下面两个要实现的接口

 

@protocol thmetadataconverter 

/**
 *  分析avmetadataitem 将数据转化为可展示的的格式
 */
- (id)displayvaluefrommetadataitem:(avmetadataitem *)item;

/**
 *  将可展示的内容转化为之前的 avmetadataitem
 */
- (avmetadataitem *)metadataitemfromdisplayvalue:(id)value
                                withmetadataitem:(avmetadataitem *)item;
@end

这两个函数的主要功能就是上面的注释!在处理avmetaitem的时候,最复杂的就是后去value值,看看有些是比较简单,比如歌名,"张震岳"直接可以取得,但是想图片,artwork,comment(评论)这些数据是要分析的。下面给出获取普通的信息代码和图片信息的代码,不展开分析。

 

@implementation thdefaultmetadataconverter

- (id)displayvaluefrommetadataitem:(avmetadataitem *)item {
    return item.value;
}

- (avmetadataitem *)metadataitemfromdisplayvalue:(id)value
                                withmetadataitem:(avmetadataitem *)item {
    
    avmutablemetadataitem *metadataitem = [item mutablecopy];
    metadataitem.value = value;
    return metadataitem;
}

@end

 

 

@implementation thartworkmetadataconverter

- (id)displayvaluefrommetadataitem:(avmetadataitem *)item {
    nsimage *image = nil;
    if ([item.value iskindofclass:[nsdata class]]) {                        // 1
        image = [[nsimage alloc] initwithdata:item.datavalue];
    }
    else if ([item.value iskindofclass:[nsdictionary class]]) {             // 2
        nsdictionary *dict = (nsdictionary *)item.value;
        image = [[nsimage alloc] initwithdata:dict[@"data"]];
    }
    return image;
}

- (avmetadataitem *)metadataitemfromdisplayvalue:(id)value
                                withmetadataitem:(avmetadataitem *)item {

    avmutablemetadataitem *metadataitem = [item mutablecopy];

    nsimage *image = (nsimage *)value;
    metadataitem.value = image.tiffrepresentation;                          // 3
    
    return metadataitem;
}

@end

 

 

我还有很多的细节没有分享,但是总体的思路是这样的,分析源码学习了很多的东西,其实这个问题没有想象中这么简单,还有一个功能是保存元数据。

二.保存元数据

前面提到过,avasset是个不可以变化的类,我们不能修改,我们要使用avassetexportsession的类来导出一个新的资源副本以及元数据的改动。avassetexportsession是用于将avasset内容根据导出预设进行编码,并将导出资源写到磁盘中。有兴趣大家可以去研究下。

 

本文

完!

 


 

 

 

 

 

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

  • ios uicollectionview实现横向滚动

    现在使用卡片效果的app很多,之前公司让实现一种卡片效果,就写了一篇关于实现卡片的文章。文章最后附有demo实现上我选择了使用uicollectionview ... [阅读全文]
  • iOS UICollectionView实现横向滑动

    本文实例为大家分享了ios uicollectionview实现横向滑动的具体代码,供大家参考,具体内容如下uicollectionview的横向滚动,目前我使... [阅读全文]
  • iOS13适配深色模式(Dark Mode)的实现

    iOS13适配深色模式(Dark Mode)的实现

    好像大概也许是一年前, mac os系统发布了深色模式外观, 看着挺刺激, 时至今日用着也还挺爽的终于, 随着iphone11等新手机的发售, ios 13系统... [阅读全文]
  • ios 使用xcode11 新建项目工程的步骤详解

    ios 使用xcode11 新建项目工程的步骤详解

    xcode11新建项目工程,新增了scenedelegate这个类,转而将原appdelegate负责的对ui生命周期的处理担子接了过来。故此可以理解为:ios... [阅读全文]
  • iOS实现转盘效果

    本文实例为大家分享了ios实现转盘效果的具体代码,供大家参考,具体内容如下demo下载地址: ios转盘效果功能:实现了常用的ios转盘效果,轮盘抽奖效果的实现... [阅读全文]
  • iOS开发实现转盘功能

    本文实例为大家分享了ios实现转盘功能的具体代码,供大家参考,具体内容如下今天给同学们讲解一下一个转盘选号的功能,直接上代码直接看viewcontroller#... [阅读全文]
  • iOS实现轮盘动态效果

    本文实例为大家分享了ios实现轮盘动态效果的具体代码,供大家参考,具体内容如下一个常用的绘图,主要用来打分之类的动画,效果如下。主要是ios的绘图和动画,本来想... [阅读全文]
  • iOS实现九宫格连线手势解锁

    本文实例为大家分享了ios实现九宫格连线手势解锁的具体代码,供大家参考,具体内容如下demo下载地址:效果图:核心代码://// clockview.m// 手... [阅读全文]
  • iOS实现卡片堆叠效果

    本文实例为大家分享了ios实现卡片堆叠效果的具体代码,供大家参考,具体内容如下如图,这就是最终效果。去年安卓5.0发布的时候,当我看到安卓全新的material... [阅读全文]
  • iOS利用余弦函数实现卡片浏览工具

    iOS利用余弦函数实现卡片浏览工具

    本文实例为大家分享了ios利用余弦函数实现卡片浏览工具的具体代码,供大家参考,具体内容如下一、实现效果通过拖拽屏幕实现卡片移动,左右两侧的卡片随着拖动变小,中间... [阅读全文]
验证码:
移动技术网