mp3ファイルからID3タグを読み込むサンプル
今度はAVFoundation Frameworkを使ってみた。
試したOSバージョンは10.7.2(Lion)
#import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> #import "AVMetadataItemAdditions.h" int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSURL *url = [NSURL fileURLWithPath:@"/var/tmp/sample.mp3"]; AVURLAsset *asset = [AVURLAsset assetWithURL:url]; if (![asset.availableMetadataFormats containsObject:AVMetadataFormatID3Metadata]) { NSLog(@"*** ID3 tags not found in %@", url); exit(-1); } NSArray *origMetadata = [asset metadataForFormat:AVMetadataFormatID3Metadata]; for (AVMetadataItem *item in origMetadata) { NSString *key = [item keyAsString]; id value = item.value; NSLog(@"value for tag:%@ is:%@", key, value); } [pool drain]; return 0; }
アセットってのが音楽や動画を扱う単位になるみたい。\オープンディール!/
AVMetadataItemクラスにkeyAsStringなんてメソッド無いよね*1。実はカテゴリで追加してます。AVMetadataItemのkeyはなぜか数値を返すので文字列にデコードしている。
// AVMetadataItemAdditions.h #import <Foundation/Foundation.h> #import <AVFoundation/AVMetadataItem.h> @interface AVMetadataItem (KeyAsStringAdditions) - (NSString *)keyAsString; @end
// AVMetadataItemAdditions.m // cf:http://developer.apple.com/library/mac/#samplecode/avmetadataeditor/Introduction/Intro.html #import "AVMetadataItemAdditions.h" @implementation AVMetadataItem (KeyAsStringAdditions) /* Get a string from a 4cc */ static NSString * stringForOSType(OSType theOSType) { size_t len = sizeof(OSType); long addr = (unsigned long)&theOSType; char cstring[5]; len = (theOSType >> 24) == 0 ? len - 1 : len; len = (theOSType >> 16) == 0 ? len - 1 : len; len = (theOSType >> 8) == 0 ? len - 1 : len; len = (theOSType >> 0) == 0 ? len - 1 : len; addr += (4 - len); theOSType = EndianU32_NtoB(theOSType); // strings are big endian strncpy(cstring, (char *)addr, len); cstring[len] = 0; return [NSString stringWithCString:(char *)cstring encoding:NSMacOSRomanStringEncoding]; } - (NSString *)keyAsString { NSString *keyAsString = nil; if ([[self key] isKindOfClass:[NSString class]]) { keyAsString = (NSString *)[self key]; } else if ([[self key] isKindOfClass:[NSNumber class]]) { NSNumber *keyAsNumber = (NSNumber *)[self key]; keyAsString = stringForOSType([keyAsNumber unsignedIntValue]); } else if ([[self key] isKindOfClass:[NSObject class]]) { keyAsString = [(NSObject *)[self key] description]; } return keyAsString; } @end
処理の内容は「avmetadataeditor」という公式のサンプルコードから拝借しました。
出力例:
2012-01-08 04:33:19.465 AVFrameworkSample[4761:707] value for tag:TT2 is:Beautiful Trick 2012-01-08 04:33:19.483 AVFrameworkSample[4761:707] value for tag:TP1 is:Vivienne 2012-01-08 04:33:19.487 AVFrameworkSample[4761:707] value for tag:TCM is:ZUN 2012-01-08 04:33:19.487 AVFrameworkSample[4761:707] value for tag:TAL is:Flower Flag 2012-01-08 04:33:19.488 AVFrameworkSample[4761:707] value for tag:TRK is:5/9 2012-01-08 04:33:19.489 AVFrameworkSample[4761:707] value for tag:TPA is:1/1 2012-01-08 04:33:19.489 AVFrameworkSample[4761:707] value for tag:TYE is:2011 2012-01-08 04:33:19.490 AVFrameworkSample[4761:707] value for tag:TCO is:Soundtrack 2012-01-08 04:33:19.490 AVFrameworkSample[4761:707] value for tag:TCP is:1 2012-01-08 04:33:19.491 AVFrameworkSample[4761:707] value for tag:COM is:{ identifier = iTunPGAP; language = eng; text = 0; } 2012-01-08 04:33:19.492 AVFrameworkSample[4761:707] value for tag:TEN is:iTunes v7.6.2.9 2012-01-08 04:33:19.496 AVFrameworkSample[4761:707] value for tag:COM is:{ identifier = iTunNORM; language = eng; text = " 0000167E 0000165A 00008A00 0000A583 00037A68 0003F12F 00008B81 00008BA8 0003B6B6 00032E2F"; } 2012-01-08 04:33:19.497 AVFrameworkSample[4761:707] value for tag:COM is:{ identifier = iTunSMPB; language = eng; text = " 00000000 00000210 00000AE0 0000000000D32210 00000000 0072DA73 00000000 00000000 00000000 00000000 00000000 00000000"; } 2012-01-08 04:33:19.546 AVFrameworkSample[4761:707] value for tag:COM is:{ identifier = "iTunes_CDDB_IDs"; language = eng; text = "9+89347F9EE1B6AC6A6FA37AEBEA836D79+31558448"; }
蛇足: mp3ファイルにID3タグを書き込むサンプル(をやってみたかった)
結局上手く行かなかったわ。何が悪いのか分からない。
既存のID3v2.2.0タグが付いている曲に「アルバムアーティスト」を追加するという想定でいく。
まず、AVMetadataItemのリストを作る。上のサンプルの要領で既存の曲から読み込んで、TP2が無ければ付ける。
.... // 元の曲のAssetに付いているID3のメタデータ NSArray *origMetadata = [asset metadataForFormat:AVMetadataFormatID3Metadata]; // 書き込む為のID3のメタデータの入れ物 NSMutableArray *newMetadata = [NSMutableArray array]; AVMutableMetadataItem *albumArtistMetadataItem = nil; for (AVMetadataItem *item in origMetadata) { AVMutableMetadataItem *newItem = [[item mutableCopy] autorelease]; if ([[newItem keyAsString] isEqual:@"TP2"] || [[newItem keyAsString] isEqual:AVMetadataID3MetadataKeyBand]) { // @"TPE2" // アルバムアーティストが既にあったら覚えておく albumArtistMetadataItem = newItem; } [newMetadata addObject:newItem]; } // アルバムアーティストが無かったら追加 if (!albumArtistMetadataItem) { albumArtistMetadataItem = [AVMutableMetadataItem metadataItem]; albumArtistMetadataItem.key = @"TP2"; albumArtistMetadataItem.keySpace = AVMetadataKeySpaceID3; [newMetadata addObject:albumArtistMetadataItem]; } // アルバームアーティスト名を設定 albumArtistMetadataItem.value = @"FELT"; ....
タグを含めAVAssetの情報を書き込むには、AVAssetExportSessionってのを使うらしい。
MP3のファイルタイプが無いのでAVFileTypeQuickTimeMovieを指定する。
presetには、中のフォーマットを変換する必要がない場合はpresetName:AVAssetExportPresetPassthroughを指定する。
.... // write tags (NOT WORKS!!) NSString *outputFileType = AVFileTypeQuickTimeMovie; NSURL *destinationUrl = [NSURL fileURLWithPath:@"/var/tmp/sample_out.mp3"]; AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetPassthrough]; if (![[session supportedFileTypes] containsObject:outputFileType]) { return -1; } [session setOutputFileType:outputFileType]; [session setOutputURL:destinationUrl]; [session setMetadata:newMetadata]; // set new metadata ....
書き込みは非同期で行われるので、フォアグラウンドスレッドで書き込み終了まで待機する。
非同期処理を同期する為のNSRunLoopの使い方はココ参照。
NSThread *foregroundThead = [NSThread currentThread]; NSObject *dummy = [[NSObject new] autorelease]; [session exportAsynchronouslyWithCompletionHandler:^{ [dummy performSelector:@selector(self) onThread:foregroundThead withObject:nil waitUntilDone:NO]; }]; while ([session status] == AVAssetExportSessionStatusExporting) { [[NSRunLoop currentRunLoop] acceptInputForMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog(@"waiting... progress:%f", [session progress]); } if (session.error) { NSLog(@"error: %@", session.error); } ....
exportAsynchronouslyWithCompletionHandlerに終了時に実行するBlockを渡せるので、そこから表のスレッドで何か実行する。ほんとに何でも良いので今回はダミーのNSObjectを作って-selfを呼んでいる。
ちなみにAVAssetExportSessionは既存のファイルを上書き出来ないみたいなので、再実行するときは出来たファイルを毎回消さないとエラーになる。
これで出力したファイルをiTunesなどで見ると……ID3タグが全く含まれていない。MP3の出力には対応していないと考えた方が良そう。
*1:_keyAsStringってメソッド見えてるけど……