Статьи

Текст в речь на Windows Phone 8

С момента утечки Windows Phone 8 SDK я копался в документации в поисках интересных дополнений к предстоящему выпуску. Проведя пару вечеров, пытаясь заставить кошелек работать в эмуляторе, используя бесполезные примеры кода, найденные в документах, я обратил свое внимание на новые классы SpeechSynthesizer и InstalledVoices.All.

Согласно моему обычному формату, я начинаю с XAML. Первое, на что могут обратить внимание разработчики WP7, это то, что ApplicationTitle и pagenames больше не жестко закодированы по умолчанию, а являются ресурсами, общими для всего приложения. Таким образом, вам больше не нужно устанавливать имя приложения на всех своих страницах по одной.

<phone:PhoneApplicationPage
    x:Class="TextToSpeechDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="{Binding Path=LocalizedResources.PageTitle, Source={StaticResource LocalizedStrings}}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <ScrollViewer Height="200">
                    <ComboBox HorizontalAlignment="Left" Width="456" Name="voicesComboBox" DisplayMemberPath="Name" />
                </ScrollViewer>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                    <RadioButton Content="Male" IsChecked="true" Name="MaleRadioButton"/>
                    <RadioButton Content="Female"/>
                </StackPanel>
                <TextBox HorizontalAlignment="Left" Height="230" TextWrapping="Wrap" Width="456" Text="I may be a sorry case, but I don't write jokes in base 13." Name="inputTextBox"/>
                <Button Content="Speak to me" HorizontalAlignment="Left" Width="456" Click="SpeakToMe_Click"/>
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

 Далее идет файл с выделенным кодом для MainPage.xaml. Его содержимое заимствовано для документации WP8 SDK. Я добавил проверку ошибок и возможность выбора пола и языка используемого голоса.

using System;
using System.Linq;
using System.Windows;
using Microsoft.Phone.Controls;
using Windows.Phone.Speech.Synthesis;
 
namespace TextToSpeechDemo
{
    public partial class MainPage : PhoneApplicationPage
    {
        SpeechSynthesizer synth;
        // Constructor
        public MainPage()
        {
            InitializeComponent();
            voicesComboBox.ItemsSource = new MyLocals().Items();
        }
 
        private async void SpeakToMe_Click(object sender, RoutedEventArgs e)
        {
            if (voicesComboBox.SelectedIndex == -1)
            {
                MessageBox.Show("Please select a language.");
            }
            else
            {
                if (string.IsNullOrEmpty(inputTextBox.Text))
                {
                    MessageBox.Show("Please enter some text.");
                }
                else
                {
                    try
                    {
                        // Initialize the SpeechSynthesizer object.
                        synth = new SpeechSynthesizer();
 
                        var myLocal = (MyLocale)voicesComboBox.SelectedItem;
 
                        // Query for a voice. Results rdered by Gender to ensure the order always goes Female then Male.
                        var voices = (from voice in InstalledVoices.All
                                      where voice.Language == myLocal.Lcid
                                      select voice).OrderByDescending(v => v.Gender);
 
                        // gender: 0 = Female, 1 = Male. Corresponds to the index of the above results.
                        int gender = 0;
                        if (MaleRadioButton.IsChecked == true) gender = 1; else gender = 0;
 
                        // Set the voice as identified by the query.
                        synth.SetVoice(voices.ElementAt(gender));
 
                        // Speak
                        await synth.SpeakTextAsync(inputTextBox.Text);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
        }
    }
}

И, наконец, пара классов, чтобы заполнить ComboBox языков. Проверка установленных голосов. Все показывает, что в нем 30 пунктов (15 языков по 2 голоса в каждом). Я не смог напрямую соперничать со списком по причинам, которые я не знаю. Все, что я знаю, это то, что это COM-объект, и единственный способ получить необходимые объекты VoiceInformation — это запрос LINQ. Я был в состоянии определить 12 языков, но нужно посмотреть глубже, чтобы узнать другие 3.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace TextToSpeechDemo
{
    class MyLocale
    {
        public MyLocale(string name, string lcid)
        {
            _name = name;
            _lcid = lcid;
        }
 
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
 
        private string _lcid;
        public string Lcid
        {
            get { return _lcid; }
            set { _lcid = value; }
        }
    }
 
    class MyLocals
    {
        private IList<MyLocale> _myLocals;
 
        public MyLocals()
        {
            _myLocals = new List<MyLocale>();
 
            _myLocals.Add(new MyLocale("Chinese Simplified (PRC)", "zh-CN"));
            _myLocals.Add(new MyLocale("Chinese Traditional (Taiwan)", "zh-TW"));
            _myLocals.Add(new MyLocale("English (United States)", "en-US"));
            _myLocals.Add(new MyLocale("English (United Kingdom)", "en-GB"));
            _myLocals.Add(new MyLocale("French (France)", "fr-FR"));
            _myLocals.Add(new MyLocale("German (Germany)", "de-DE"));
            _myLocals.Add(new MyLocale("Italian (Italy)", "it-IT"));
            _myLocals.Add(new MyLocale("Japanese (Japan)", "ja-JP"));
            _myLocals.Add(new MyLocale("Polish (Poland)", "pl-PL"));
            _myLocals.Add(new MyLocale("Portuguese (Brazil)", "pt-BR"));
            _myLocals.Add(new MyLocale("Russian (Russia)", "ru-RU"));
            _myLocals.Add(new MyLocale("Spanish (Spain)", "es-ES"));
        }
 
        public IEnumerable<MyLocale> Items()
        {
            return (IEnumerable<MyLocale>)_myLocals;
        }
    }
}

Далее нужно установить возможность ID_CAP_SPEECH_RECOGNITION, в противном случае выдается исключение.

 

Теперь, если мы запустим приложение, нам будет показано следующее. Выбор языка и нажатие кнопки приведут к тому, что эмулятор начнет говорить с вами.

Я заметил одну вещь: если я выбираю не английский язык и помещаю текст на английском языке, он говорит с акцентом. Когда я устанавливаю текст на французский 1, 2, 3, 4 и устанавливаю другой аналогичный язык (испанский), он произносит французский «4» на испанском, а не на французском. Интересное событие:)