LoginSignup
10
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-06-06

簡易RSSリーダーを作ってみた 〜 Objective-C編 〜》のMacOS X 用サンプルコードをほんの少し改造して、Reuters: トップニュースKyokoさんに読み上げてもらいました。

テキストの読み上げ

OS X の Command Line Tool でテキストの読み上げを行うためには、AppKit をインポートして NSSpeechSynthesizer を使います。

#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を使って取得した記事から読み上げ可能なテストを抽出します。

 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フィードを読み上げるアプリを作っています (O▽O)

【YouTubeデモ】
IMAGE ALT TEXT HERE

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7