PowerShell Remoting Project Home

Saturday, February 04, 2006

Give Monad a Voice: out-voice cmdlet

This script will get objects from pipline and output as voice using Text To Speech (TTS) engine. If set $SaveToWav to $true, it will output to a serial of WAV files.

WindowsXP shipped with both speech recognition and Text To Speech Engine. TTS engine (sapi.dll) takes text as input and output that text as spoken audio. Although WinFX will contain the fully managed API for speech, at this moment, I have to use (ugly) com interop. To make things easier, I generate a managed API libaray (SpeechLib.dll) using tlbimp.exe, a tool in .NET 2.0 SDK.
set-alias tlbimp "C:\Program Files\Microsoft.NET\SDK\v2.0\bin\tlbimp.exe"
tlbimp "C:\Program Files\Common Files\Microsoft Shared\Speech\sapi.dll" `
/out:SpeechLib.dll /namespace:SpeechLib
You will see some warnings talking about type error. Just ignore them because we will not use those problemetic class today. Then we can load the libray, initialize a SpeechLib.SpVoiceClass object and call Speak method.
[Reflection.Assembly]::LoadFile("D:\msh\SpeechLib.dll")
$SpVoice = new-object SpeechLib.SpVoiceClass
$SpVoice.Speak("Hello", [SpeechLib.SpeechVoiceSpeakFlags]::SVSFDefault)
After using this cmdlet for a while, I found out that call Speak method asynchronously is a good idea. While listen to your voice output (which could be lengthy and boring), you can still get a prompt and working with MSH.
$SpVoice.Speak("Hello", [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
To get a .wav filename, I used System.Windows.Forms.SaveFileDialog object
$SaveFileD = new-object System.Windows.Forms.SaveFileDialog
$SaveFileD.Filter = "All files (*.*)|*.*|wav files (*.wav)|*.wav";
$SaveFileD.Title = "Save to a wave file";
$SaveFileD.FilterIndex = 2;
$SaveFileD.RestoreDirectory = true;
if ($SaveFileD.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
{
$FileName = $SaveFileD.FileName
}
To save to wav file, I use SpeechLib.SpFileStreamClass
$WaveFile = new-object SpeechLib.SpFileStreamClass
$WaveFile.Open($FileNameS, [SpeechLib.SpeechStreamFileMode]::SSFMCreateForWrite, $false)
$SpVoice.AudioOutputStream = $WaveFile
$SpVoice.Speak($StringToSay, [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
$SpVoice.WaitUntilDone([System.Threading.Timeout]::Infinite)
$WaveFile.Close()
I use $_ to get object from pipline and use out-string cmdlet to convert them to System.String object .
$StringToSay = $_ | out-string
If you want to try it, do something like
dir| select-object Name -first 4 | .\out-voice.msh
get-eventlog Application -Newest 4 | .\out-voice.msh $true
Make sure the speaker on your computer is poweron and enabled. Make sure you use select to format your results first. Otherwise you won't know what your computer are talking about. There is no guarantee that it would be fun. As I said before it could be lengthy and boring.
###############################################################
#
# out-voice.msh
# This script takes objects from pipline and output as voice using TTS engine.
# If set $SaveToWav to $true, output to WAV file(s).
#
# Usage: "Hello", "Welcome to monad world" | out-voice
#        "Hello", "Welcome to monad world" | out-voice $true
#
# from Tony http://mshforfun.blogspot.com/
#
################################################################

param ( [bool] $SaveToWav = $false )
begin
{   
    [void][Reflection.Assembly]::LoadFile("D:\msh\SpeechLib.dll")
    $SpVoice = new-object SpeechLib.SpVoiceClass
    $count = 0
    if ($SaveToWav)
    {
        [void] [Reflection.Assembly]::Load("System.Windows.Forms, Version=2.0.0.0, `
        Culture=neutral, PublicKeyToken=b77a5c561934e089")
        $SaveFileD = new-object System.Windows.Forms.SaveFileDialog
        $SaveFileD.Filter = "All files (*.*)|*.*|wav files (*.wav)|*.wav";
        $SaveFileD.Title = "Save to a wave file";
        $SaveFileD.FilterIndex = 2;
        $SaveFileD.RestoreDirectory = true;
        if ($SaveFileD.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
        {
        $FileName = $SaveFileD.FileName
        }
       else
      {
        "Must Specify a wav file"
        break
      }
   }
}
process
{
    if ($_)
    {
       $count++
       $StringToSay = $_ | out-string
       "$count: $StringToSay"
       if ($SaveToWav)
       {
            $FileNameS = $FileName.Replace(".wav", $count.Tostring()+".wav")
            $WaveFile = new-object SpeechLib.SpFileStreamClass
            $WaveFile.Open($FileNameS, `
            [SpeechLib.SpeechStreamFileMode]::SSFMCreateForWrite, $false)
            $SpVoice.AudioOutputStream = $WaveFile
            [void]$SpVoice.Speak($StringToSay, `
            [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
            [void]$SpVoice.WaitUntilDone([System.Threading.Timeout]::Infinite)
            $WaveFile.Close()
       }
       else
       {
            [void]$SpVoice.Speak($StringToSay, `
            [SpeechLib.SpeechVoiceSpeakFlags]::SVSFlagsAsync)
       }
    }
    else
    {
       "null object"
    }
}
end
{
  "Number of objects successfully output: $count"
}
##############################################
Reference: Code4Fun, C# Corner

Have Fun!

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Tags:       


Comments:

Post a Comment





<< Home