nakamurakko’s blog

仕事で覚えたこと、勉強したことを自分のメモ代わりに書いていこうかなと。

テキスト情報を読み上げるUWPアプリケーションを作成する

AzureのAI関連を使ってみたくて、関連書籍を探したことがあった。

Cognitive Services入門 マイクロソフト人工知能APIの使い方

Cognitive Services入門 マイクロソフト人工知能APIの使い方

Cognitive Servicesの基礎が自分に無いので、「ふむふむ、なるほど、難しい…」と、使うというよりは読み物としてサクサク読み進めながら「第4章 音声のAPI」に突入した時、ふと「これはCognitive Servicesを経由しなくてもアプリケーションに組み込めるのでは」と思い、試してみた。


2019/03/11 追記

Ayako さんから、Cognitive Servicesのサンプルを教えていただきました。ありがとうございます。

Microsoft Cognitive Services を利用した 音声⇔テキスト変換サンプル (201907 版) https://github.com/ayako/CogServicesSpeechSamples_201907

環境

  • Windows 10
  • Visual Studio 2019
  • Prism.Core

プロジェクト作成

UWPのプロジェクトを作成し、Prismの機能をを使いたいので「NuGetパッケージ管理」でPrism.Coreを追加する。

f:id:nakamurakko:20200309193309p:plain

メインページ修正

MainPage.xamlを修正していく。コントロールは、

  • ①読み上げボタン
  • ②音声選択コンボボックス
  • ③読み上げたいテキストを入れるテキストボックス

を、用意して、入力されたテキストを選択した音声で読み上げるようにした。

<Page x:Class="TextReader.Views.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TextReader"
      xmlns:viewModels="using:TextReader.ViewModels"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="Margin"
                    Value="5" />
        </Style>

        <Style TargetType="ComboBox">
            <Setter Property="Margin"
                    Value="5" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin"
                    Value="5" />
        </Style>
    </Page.Resources>

    <Page.DataContext>
        <viewModels:MainViewModel />
    </Page.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- ①テキストを読み上げるコマンドを呼び出すボタン。 -->
            <Button Grid.Column="0"
                    Content="読み上げ"
                    Command="{Binding ReadTextCommand}" />

            <TextBlock Grid.Column="1"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Text="音声選択" />

            <!-- ②音声を選択するコンボボックス。 -->
            <ComboBox Grid.Column="2"
                      HorizontalAlignment="Stretch"
                      ItemsSource="{Binding Voices}"
                      SelectedItem="{Binding SelectedVoice, Mode=TwoWay}">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding DisplayName}" />
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Grid>

        <!-- ③読み上げたいテキスト。 -->
        <TextBox Grid.Row="1"
                 TextWrapping="Wrap"
                 AcceptsReturn="True"
                 Text="{Binding TargetText, Mode=TwoWay}"
                 ScrollViewer.VerticalScrollBarVisibility="Auto" />

    </Grid>
</Page>

ViewModel

メインページのDataContextで使用しているMainViewModelクラスを追加する。 追加した後、Prism.Mvvm.BindableBaseを継承する。

public class MainViewModel : BindableBase

読み上げるテキストを保持するプロパティとして、TargetTextプロパティを用意する。 SetPropertyはBindableBaseを継承して使えるようになる、プロパティ変更通知をうまくやってくれるやつ。

private string targetText = "";

/// <summary>
/// 読み上げたいテキスト。
/// </summary>
public string TargetText
{
    get { return targetText; }
    set { SetProperty(ref targetText, value); }
}

音声選択のコンボボックスの音声一覧(Voices)と、コンボボックスの選択値を保持するプロパティ(SelectedVoice)を用意する。 Voicesの型パラメーターにはWindows.Media.SpeechSynthesis.VoiceInformationを指定する。

private ObservableCollection<VoiceInformation> voices;

/// <summary>
/// 音声の一覧。
/// </summary>
public ObservableCollection<VoiceInformation> Voices
{
    get { return voices; }
    private set { SetProperty(ref voices, value); }
}

private VoiceInformation selectedVoice;

/// <summary>
/// 選択した音声。
/// </summary>
public VoiceInformation SelectedVoice
{
    get { return selectedVoice; }
    set { SetProperty(ref selectedVoice, value); }
}

コンストラクターでコンボボックス用プロパティを初期化する。

/// <summary>
/// コンストラクター。
/// </summary>
public MainViewModel()
{
    // ①音声の一覧を取得する。
    Voices = new ObservableCollection<VoiceInformation>(SpeechSynthesizer.AllVoices);
    // ②使用出来る音声がある場合、音声情報の1つ目を選択する。
    SelectedVoice = Voices.Count > 0 ? Voices[0] : null;
}

①で音声の一覧を取得する。取得元はWindows.Media.SpeechSynthesis.AllVoices。 日本語以外の音声を使いたい場合は、Windowsの設定の「時刻と言語」-「音声認識」-「音声を追加」で追加する。

f:id:nakamurakko:20200309193345p:plain

②で取得出来た音声情報の1番目を選択。

今回重要な、入力したテキストと選択した音声情報を読み上げる処理を作成については、サンプルがWindows.Media.SpeechSynthesis.SpeechSynthesizer Classのサイトに載っているので、ほぼコピーして使う。 サンプルを参考に作成したコマンドはこちら。

private DelegateCommand readTextCommand;

/// <summary>
/// 読み上げCommand。
/// </summary>
public ICommand ReadTextCommand
{
    get
    {
        readTextCommand = readTextCommand ?? new DelegateCommand(async () => await ExecuteReadTextCommandAsync());
        return readTextCommand;
    }
}

private async Task ExecuteReadTextCommandAsync()
{
    if (string.IsNullOrWhiteSpace(this.TargetText))
    {
        return;
    }

    var synth = new SpeechSynthesizer();
    MediaElement mediaElement = new MediaElement();
    synth.Voice = SelectedVoice;
    SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(targetText);
    mediaElement.SetSource(stream, stream.ContentType);
    mediaElement.Play();
}

ReadTextCommandを読み上げボタンにバインドさせて、ExecuteReadTextCommandAsyncを実行する。 SpeechSynthesizer.SynthesizeTextToStreamAsyncが非同期のため、async、awaitを使っている。(合っているはず!)

実行してみる

作成したアプリケーションを起動して、読み上げさせたいテキストを入力した結果がこちら。

f:id:nakamurakko:20200309193404p:plain

読み上げボタンをクリックしてテキストを読んでくれれば確認完了。

ちなみに、音声を「Microsoft Ayumi」などの日本語を選択して「This is a pen」を読ませると、「ディス イズ エー ペン」になってしまう。仕方ないんだろうけど、少し面白かった。