MacでKyokoさんにニュースを読んでもらった


《[簡易RSSリーダーを作ってみた 〜 Objective-C編 〜](http://qiita.com/tanuki-project/items/8dd4530ab627831950eb)》のMacOS X 用サンプルコードをほんの少し改造して、[Reuters: トップニュース](http://feeds.reuters.com/reuters/JPTopNews)を[Kyokoさん](http://news.mynavi.jp/column/osxhack/038/)に読み上げてもらいました。

#テキストの読み上げ

OS X の Command Line Tool でテキストの読み上げを行うためには、AppKit をインポートして [NSSpeechSynthesizer](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSSpeechSynthesizer_Class/History.html) を使います。

    #import <AppKit/AppKit.h>

NSSpeechSynthesizer の生成・初期化は以下のような感じで音声、読み上げ速度、音量を指定します。

    NSSpeechSynthesizer *speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
    [speechSynth setVoice:@"com.apple.speech.synthesis.voice.kyoko"];
    [speechSynth setRate:240];
    [speechSynth setVolume:0.9];

読み上げは startSpeakinkString で開始して、一旦スリープ(しないと読み上げが開始されない模様)、読み上げが完了して isSpeaking の値がNOになるまで待ちます (ちなみに、Cocoaアプリだと読み上げが終わると didFinishSpeaking が呼び出される)。

    [speechSynth startSpeakingString:title];
    sleep(1);
    while([speechSynth isSpeaking] == YES) {
        usleep(1000);
    }

#テキストの抽出

RSSフィードの記事の中にはリンク情報なども含まれているので、それらをそのままNSSpeechSynthesizerに渡すと、意味不明な呪文の詠唱が行われてしまいます。それでは困るので(description)を [NSAttributedString](https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/index.html)を使って取得した記事から読み上げ可能なテストを抽出します。

     NSData *data = [description dataUsingEncoding:NSUTF16StringEncoding];
     NSAttributedString *as = [[NSAttributedString alloc] initWithHTML:data options:nil documentAttributes:nil];

#サンプルコード

まあ、こんなものでしょうか(手抜き)。

	#import <Foundation/Foundation.h>
	#import <AppKit/AppKit.h>
	
	#define PATH @"http://feeds.reuters.com/reuters/JPTopNews"
	
	int speech_rss(NSXMLDocument*);
	
	int main(int argc, const char * argv[]) {
	    @autoreleasepool {
	        // Download RSS feed.
	        NSURL           *url = [NSURL URLWithString:PATH];
	        NSURLRequest    *urlRequest = [NSURLRequest requestWithURL:url
	                                                       cachePolicy:NSURLRequestReloadRevalidatingCacheData
	                                                   timeoutInterval:30];
	        
	        NSData			*urlData;
	        NSURLResponse	*response;
	        NSError			*error;
	        urlData = [NSURLConnection sendSynchronousRequest:urlRequest
	                                        returningResponse:&response
	                                                    error:&error];
	        if (!urlData) {
	            printf("failed to url request.");
	        }
	        //NSString* xmlString = [[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding];
	        //NSLog(@"doc\n%@\n", xmlString);
	
	        // Create XML Document fron urlData
	        NSXMLDocument	*doc;
	        doc = [[NSXMLDocument alloc] initWithData:urlData
	                                          options:0
	                                            error:&error];
	        if (!doc) {
	            printf("failed to create xml doc.");
	            return 1;
	        }
	        
	        // speech RSS Feed.
	        speech_rss(doc);
	        return 0;
	    }
	}
	
	int speech_rss(NSXMLDocument	*doc) {
	    NSError			*error;
	
	    // Initialize speech Synthesizer.
	    NSSpeechSynthesizer *speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
	    [speechSynth setVoice:@"com.apple.speech.synthesis.voice.kyoko"];
	    [speechSynth setRate:240];      // default value : 180.0
	    [speechSynth setVolume:0.9];    // default value : 1.0
	    //NSLog(@"speechSynth: %f %f %@", [speechSynth rate], [speechSynth volume], [speechSynth voice]);
	
	    // Get and print Title of RSS Feed.
	    NSArray *channelNodes = [doc nodesForXPath:@"//channel" error:&error];
	    if (!channelNodes) {
	        return -1;
	    }
	    for (NSXMLElement* element in channelNodes) {
	        if ([element kind] != NSXMLElementKind) {
	            continue;
	        }
	        NSArray*    children = [element children];
	        NSString*   title;
	        for (NSXMLNode* node in children) {
	            if ([[node name] isEqualToString:@"title"]) {
	                title = [node stringValue];
	                printf("\nTitle: %s\n\n", [title cStringUsingEncoding:NSUTF8StringEncoding]);
	                [speechSynth startSpeakingString:title];
	                sleep(1);
	                while([speechSynth isSpeaking] == YES) {
	                    usleep(1000);
	                }
	            }
	        }
	    }
	    
	    // Get Node list of RSS items.
	    NSArray *itemNodes = [doc nodesForXPath:@"//item" error:&error];
	    if (!itemNodes) {
	        return -1;
	    }
	    for (NSXMLElement* element in itemNodes) {
	        if ([element kind] != NSXMLElementKind) {
	            continue;
	        }
	        NSArray*    children = [element children];
	        NSString*   title;
	        NSString*   description;
	        for (NSXMLNode* node in children) {
	            if ([[node name] isEqualToString:@"title"]) {
	                title = [node stringValue];
	            } else if ([[node name] isEqualToString:@"description"]) {
	                description = [node stringValue];
	            }
	        }
	        
	        // Extract readable text.
	        printf("title:   %s\n", [title cStringUsingEncoding:NSUTF8StringEncoding]);
	        NSString* speechText = nil;
	        NSData *data = [description dataUsingEncoding:NSUTF16StringEncoding];
	        NSAttributedString *as = [[NSAttributedString alloc] initWithHTML:data options:nil documentAttributes:nil];
	        if (as) {
	            speechText = [[NSString alloc] initWithFormat:@"%@ \n %@ ", title, [as string]];
	            printf("article: %s\n", [[as string] cStringUsingEncoding:NSUTF8StringEncoding]);
	        } else {
	            speechText = [[NSString alloc] initWithFormat:@"%@ \n %@ ", title, description];
	            printf("article: %s\n", [description cStringUsingEncoding:NSUTF8StringEncoding]);
	        }
	
	        // Speach extracted text.
	        [speechSynth startSpeakingString:speechText];
	        sleep(1);
	        while([speechSynth isSpeaking] == YES) {
	            usleep(10000);
	        }
	        usleep(10000);
	    }
	    return 0;
	}

#デモ

**たぬきぷろじぇくと**では、これを使ってRSSフィードを読み上げる[アプリ](https://itunes.apple.com/jp/app/furippufido-lite/id668578254?mt=12)を作っています (O▽O)

【YouTubeデモ】
[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/XS-1kCwSDJg/0.jpg)](http://www.youtube.com/watch?v=XS-1kCwSDJg)