Scott
Scott

Reputation: 11

Powershell read file live and output to speech

What i am looking for is to take powershell and read the file content out to the speech synthesis module.

File name for this example will be read.txt.

Start of the Speech module:

Add-Type -AssemblyName System.speech

$Narrator1 = New-Object System.Speech.Synthesis.SpeechSynthesizer

$Narrator1.SelectVoice('Microsoft Zira Desktop')

$Narrator1.Rate = 2

$Location = "$env:userprofile\Desktop\read.txt"

$Contents = Get-Content $Location

Get-Content $Location -wait -Tail 2 | where {$Narrator1.Speak($Contents)}

This works once. I like to use the Clear-Content to wipe the read.txt after each initial read and have powershell wait until new line is added to the read.txt file then process it again to speak the content. I believe I can also make it run in the background with -windowstyle hidden

Thank you in advanced for any assistance.

Scott

Upvotes: 0

Views: 591

Answers (2)

mklement0
mklement0

Reputation: 438273

Your only problem was that you accidentally used the previously assigned $Contents variable in the where (Where-Object) script block rather than $_, the automatic variable representing the current pipeline object:

Get-Content $Location -Wait -Tail 2 | Where-Object { $Narrator1.Speak($_) }

Get-Content $Location -Wait will poll the input file ($Location here) every second to check for new content and pass it through the pipeline (the -Tail argument only applies to the initial reading of the file; as new lines are added, they are all passed through).

The pipeline will stay alive indefinitely - until you delete the $Location file or abort processing.

Since the command is blocking, you obviously need another session / process to add content to file $Location, such as another PowerShell window or a text editor that has the file open and modifies its content.

You can keep appending to the file with >>, but that will keep growing it.

To discard the file's previous content, you must indeed use Clear-Content, as you say, which truncates the existing file without recreating it, and therefore keeps the pipeline alive; e.g.:

Clear-Content $Location
'another line to speak' > $Location

Caveat: Special chars. such as ! and ? seem to cause silent failure to speak. If anyone knows why, do tell us. The docs offer no immediate clues.


As for background operation:

With a background job, curiously, the Clear-Content / > combination appears not to work; if anybody knows why, please tell us.

However, using >> - which grows the file - does work.

The following snippet demonstrates the use of a background job to keep speaking input as it is being added to a specified file (with some delay), until a special end-of-input string is sent:

# Determine the input file (on the user's desktop)
$file = Join-Path ([environment]::GetFolderPath('Desktop')) 'read.txt'

# Initialize the input file.
$null > $file

# Define a special string that acts as the end-of-input marker.
$eofMarker = '[quit]'

# Start the background job (PSv3+ syntax)
$job = Start-Job { 
  Add-Type -AssemblyName System.speech

  $Narrator1 = New-Object System.Speech.Synthesis.SpeechSynthesizer

  $Narrator1.SelectVoice('Microsoft Zira Desktop')

  $Narrator1.Rate = 2

  while ($true) { # A dummy loop we can break out of on receiving the end-of-input marker
    Get-Content $using:file -Wait | Where-Object { 
      if ($_ -eq $using:eofMarker) { break } # End-of-input marker received -> exit the pipeline.
      $Narrator1.Speak($_) 
    }
  }

  # Remove the input file.
  Remove-Item -ErrorAction Ignore -LiteralPath $using:file

}

# Speak 1, 2, ..., 10
1..10 | ForEach-Object {
  Write-Verbose -Verbose $_
  # !! Inexplicably, using Clear-Content followed by > to keep
  # !! replacing the file content does *not* work with a background task.
  # !! >> - which *appends* to the file - does work, however.
  $_ >> $file
}

# Send the end-of-input marker to make the background job stop reading.
$eofMarker >> $file

# Wait for background processing to finish.
# Note: We'll get here long before the background job has finished speaking.
Write-Verbose -Verbose 'Waiting for processing to finish to cleanup...'
$null = Receive-Job $job -wait -AutoRemoveJob

Upvotes: 0

Dave Sexton
Dave Sexton

Reputation: 11188

I don't think a loop is the answer, I would use the FileSystemWatcher to detect when the file has changed. Try this:

$fsw = New-Object System.IO.FileSystemWatcher
$fsw.Path = "$env:userprofile\Desktop"
$fsw.Filter = 'read.txt'

Register-ObjectEvent -InputObject $fsw -EventName Changed  -Action {
    Add-Type -AssemblyName System.speech
    $Narrator1 = New-Object System.Speech.Synthesis.SpeechSynthesizer
    $Narrator1.SelectVoice('Microsoft Zira Desktop')
    $Narrator1.Rate = 2

    $file = $Event.SourceEventArgs.FullPath

    $Contents = Get-Content $file
    $Narrator1.Speak($Contents)
} 

Upvotes: 1

Related Questions