読者です 読者をやめる 読者になる 読者になる

The jonki

呼ばれて飛び出てじょじょじょじょーんき

【iOS】Google TTSでText To Speechを実装する

iOSでTTS (Text-To-Speech)、文字の読み上げができないかと思ってググるとこんなページが見つかる。

Is it possible to use TTS in iOS

この掲示板で挙げられてる中で一番簡単そうなのはGoogle 翻訳でも使われてるやつ。GETで喋らせたいテキストを渡すだけだし。というのでこんなの作りました。テキストフィールド内の日英の文字を読み上げてくれます。



GitHubにソースを上げました。
https://github.com/jojonki/iOS/tree/master/GoogleTTS


方法は簡単でこういったURLにHTTP GETするだけです。で、それをAVAudioPlayerに食わせるだけでしゃべるようになりました。日本語と英語が混じったのだと英語では読んでくれないので日本語読みをするようにします。日英判定(半角全角判定)はこちら様のサイトに感謝です。

NSStringの全角/半角バリデーションチェック


コードが冗長ですが、今回は単純な使い方の紹介なので気にせず。

GoogleTTSViewController.h
#import <UIKit/UIKit.h>
#import "GoogleTTS.h"

@interface GoogleTTSViewController : UIViewController

@property (strong, nonatomic) IBOutlet UITextField *ttsTextField;
- (IBAction)didEndOnExitTextField:(id)sender;
- (IBAction)pushSpeechButton:(id)sender;

@property GoogleTTS *tts;
@property (strong, nonatomic) IBOutlet UISwitch *autoSwitch;

- (IBAction)autoDidChange:(id)sender;
@property (strong, nonatomic) IBOutlet UISwitch *jpSwitch;
- (IBAction)jpDidChange:(id)sender;
@property (strong, nonatomic) IBOutlet UISwitch *enSwitch;
- (IBAction)enDidChange:(id)sender;

@end
GoogleTTSViewController.m
#import "GoogleTTSViewController.h"

@interface GoogleTTSViewController ()

@end

@implementation GoogleTTSViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    _tts = [[GoogleTTS alloc] init];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)didEndOnExitTextField:(id)sender {
    [_ttsTextField resignFirstResponder];
}

- (IBAction)pushSpeechButton:(id)sender {
    NSString *lang;
    NSString *str = _ttsTextField.text;
    if([_autoSwitch isOn]) {
        if([self isOnlyHalf:str]) {
            lang = @"en";
        } else {
            lang = @"ja";
        }
    } else {
        if([_jpSwitch isOn]) {
            lang = @"ja";
        } else {
            lang = @"en";
        }
    }
    [_tts convertTextToSpeech:str :lang];
}

- (void)viewDidUnload {
    [self setTtsTextField:nil];
    [self setJpSwitch:nil];
    [self setEnSwitch:nil];
    [self setAutoSwitch:nil];
    [super viewDidUnload];
}

- (IBAction)jpDidChange:(id)sender {
    if([_jpSwitch isOn]) {
        [_enSwitch setOn:NO animated:YES];
        [_autoSwitch setOn:NO animated:YES];
    } else {
        [_enSwitch setOn:YES animated:YES];
    }
}

- (IBAction)enDidChange:(id)sender {
    if([_enSwitch isOn]) {
        [_jpSwitch setOn:NO animated:YES];
        [_autoSwitch setOn:NO animated:YES];
    } else {
        [_jpSwitch setOn:YES animated:YES];
    }
}

- (IBAction)autoDidChange:(id)sender {
    if([_autoSwitch isOn]) {
        [_jpSwitch setOn:NO animated:YES];
        [_enSwitch setOn:NO animated:YES];
    } else {
        [_jpSwitch setOn:YES animated:YES];
        [_enSwitch setOn:NO animated:YES];
    }
}

// ref. http://d.hatena.ne.jp/shu223/20110219/1299330910
// 1文字ずつURIエンコードして4文字以上になったら全角とする
- (BOOL)isOnlyHalf:(NSString *)str {
	for(int i=0; i<[str length]; i++) {
		NSString *aChar = [str substringWithRange:NSMakeRange(i, 1)];
		NSString *encodedChar = [aChar stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
		NSLog(@"%@, %@", aChar, encodedChar);
        if ([encodedChar length] < 4) {
			continue;
		}
		else {
			return NO;
		}
	}
	return YES;
}

@end
GoogleTTS.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

@interface GoogleTTS : NSObject

@property NSMutableData *downloadedData;
@property AVAudioPlayer *player;

- (void)convertTextToSpeech:(NSString *)searchString:(NSString *)lang;
- (void)receivedAudio:(NSMutableData *)data;

@end
GoogleTTS.m
#import "GoogleTTS.h"

@implementation GoogleTTS

- (id)init {
    self = [super init];
    return self;
}

- (void)convertTextToSpeech:(NSString *)searchString:(NSString *)lang{
    _downloadedData = [[NSMutableData alloc] initWithLength:0];
    
    
    searchString = [searchString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSString *search = [NSString stringWithFormat:@"http://translate.google.com/translate_tts?tl=%@&q=%@", lang, searchString];
    search = [search stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
    NSLog(@"Search: %@", search);
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:search]];
    [request setValue:@"Mozilla/5.5" forHTTPHeaderField:@"User-Agent"];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
	NSInteger statusCode = [res statusCode];
    NSLog(@"Status Code: %d", statusCode);
	[self.downloadedData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	[self.downloadedData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"Failure");
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self receivedAudio:self.downloadedData];
}

- (void)receivedAudio:(NSMutableData *)data {
    _player = [[AVAudioPlayer alloc] initWithData:data error:nil];
    [_player play];
}

@end