Functions/Convert-TextToSpeech.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Function Convert-TextToSpeech        {
<#
.Synopsis
    Converts text files into audio files.
.DESCRIPTION
    Parses the contents of multiple text files and outputs each file to an audio file by using Amazon's Polly service (https://aws.amazon.com/polly/)
 
.NOTES
    Name: Convert-TextToSpeech
    Author: Chad Miles
    DateUpdated: 2017-08-16
    Version: 1.3.0
 
.EXAMPLE
   Convert-TextToSpeech input.txt -Voice Joanna
 
   This will output input.mp3 using Amy voice
.EXAMPLE
   Convert-TextToSpeech *.txt -Voice Amy -OutputFormat ogg
    
   This will convert all the .txt files to .ogg files in Joanna voice.
#>

    [CmdletBinding()]
    [Alias('tts')]
    Param (
        # The Input file(s), wilcards and just pointing to a folder itself work fine, just know that it will suck in what you ever you give it.
        [Parameter(ParameterSetName='Files')]
        [string[]]  $InputFiles, 
        # Format of the output audio in either mp3, ogg or pcm
        [ValidateSet('mp3', 'ogg', 'pcm')]
        [string]    $OutputFormat = "mp3",
        # The voice used, default is Amy (English - UK), for all voices please run Get-POLVoice | select -ExpandProperty Id
        [string]    $Voice = 'Amy',
        # Just process a String not a written file
        [Parameter(ParameterSetName='String')]
        [string[]]  $String,
        # Specify the Base name of the file to output to, without the extention
        [Parameter(ParameterSetName='String')]
        [string]    $OutputFile='Output',
        [switch]    $PlayOutput,
        [ValidateSet('x-fast','x-slow','fast', 'slow', 'medium')]
        [string]    $Speed = 'medium'
        
    )
    if ($OutputFormat -eq 'pcm'){$FileExtension = 'wav'}
    else {$FileExtension = $OutputFormat}
    $ErrorActionPreference = 'Stop'
    Write-Verbose 'Validating Voice'
    $VoiceIds   = (Get-POLVoice).Id.Value
    if ($VoiceIds -notcontains $Voice) {
        Write-Error "$Voice is not a valid voice, Valid voices are $VoiceIds"
    }
    $Speed          = $Speed.ToLower()
    $PreText        = '<speak><prosody rate="'+$Speed+'">'
    $PostText       = '</prosody><break time="350ms"/></speak>'
    $PollyLimit     = 3000
    if ($InputFiles){
        Foreach ($InputFile in Get-ChildItem $InputFiles) {
            $Text       = Get-Content $InputFile
            $LineCount     = 1
            Write-Verbose "Checking $InputFile for long lines"
            Foreach ($Line in $Text){
                $Line   = $Line.Replace('&',' and ').Replace(' ',' ')
                If ($Line.Length -ge $PollyLimit-($PreText.Length + $PostText.Length)){
                    $ShortName = $InputFile.Name
                    Write-Warning "$ShortName was skipped as Line $LineCount is longer than $PollyLimit characters, which is the longest we can submit to Polly excluding SSML tags and spaces"
                    $LongLines = $true
                }
                $LineCount++
            }
            If (!$LongLines){
                Write-Verbose "Processing $InputFile"
                $LineCount    = 1
                $BaseName     = $InputFile.BaseName
                $Directory    = $InputFile.Directory
                $OutputFile   = "$Directory\$BaseName.$FileExtension"
                $OutputStream = New-Object System.IO.FileStream $OutputFile, Create
                Try {
                    Foreach ($Line in $Text){
                        $Line         = $Line.Replace('&',' and ').Replace(' ',' ')
                        (Get-POLSpeech -Text $PreText$Line$PostText -TextType ssml -VoiceId $Voice -OutputFormat $OutputFormat).AudioStream.CopyTo($OutputStream)
                        $LineCount++
                    }
                } Catch {
                    Write-Host -ForegroundColor Red "Error while processing file"$InputFile.FullName" in Line "$LineCount":" $PSItem.Exception.InnerException
                    $OutputStream.Close()
                    $ProcessingFailed = $true
                } 
                If (!$ProcessingFailed){
                    (Get-POLSpeech -Text '<speak><break time="2s"/></speak>' -TextType ssml -VoiceId $Voice -OutputFormat $OutputFormat).AudioStream.CopyTo($OutputStream)
                    $OutputStream.Close()
                    $OutputProperties = @{
                        OutputFile    = "$BaseName.$FileExtension"
                        InputFile     = $InputFile.Name
                    }
                    New-Object -TypeName PSObject -Property $OutputProperties
                } else {if ( $ProcessingFailed) {Clear-Variable ProcessingFailed}}
            } else {if ($LongLines) {Clear-Variable -Name LongLines}}
        }
    }
    If ($String){
        $LineCount = 0
        $Dir = (Get-ChildItem -Path .).Directory
        foreach ($Line in $String){
            $LineCount++
            $Line   = $Line.Replace('&',' and ').Replace(' ',' ')
            If ($Line.Length -ge $PollyLimit-($PreText.Length + $PostText.Length)){
                Write-Error "String input at line $LineCount longer than $PollyLimit characters, which is the longest we can submit to Polly excluding SSML tags and spaces"
            }
        }
        $NewItem      = New-Item ".\$OutputFile.$FileExtension" -Force
        $OutputFile   = $NewItem.FullName
        $OutputStream = New-Object System.IO.FileStream $OutputFile, Create
        $LineCount = 0
        foreach ($Line in $String){
            $LineCount++
            $Line   = $Line.Replace('&',' and ').Replace(' ',' ')
            Try {
                (Get-POLSpeech -Text $PreText$Line$PostText -TextType ssml -VoiceId $Voice -OutputFormat $OutputFormat).AudioStream.CopyTo($OutputStream)
            } Catch {
                Write-Error "Error while processing the String in Line $LineCount : "$_.Exception.InnerException
                $OutputStream.Close()
                $ProcessingFailed = $true
            }
        } 
        If (!$ProcessingFailed){
            $OutputStream.Close()
            $OutputProperties = 
            New-Object -TypeName PSObject -Property @{OutputFile = $NewItem.Name}
        } else {
            if ($ProcessingFailed) {
                Clear-Variable ProcessingFailed
            }
        }
        If ($PlayOutput) {
            & $OutputFile
        }
    }
}