powershell-google-tts.psm1

$baseUrl = 'https://texttospeech.googleapis.com/v1/';

function Get-GoogleTTSVoices() {
  if ($null -eq $env:GOOGLE_API_KEY_TTS) {
    throw 'You must set the GOOGLE_API_KEY_TTS environment variable to use GoogleTTS.'
  }

  return Invoke-WebRequest -Uri ($baseUrl + 'voices?key=' + $env:GOOGLE_API_KEY_TTS) -ContentType 'application/json' -Method GET | ConvertFrom-Json;
}

$completer = {
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  $upper = $wordToComplete.ToUpper()
  (Get-GoogleTTSVoices).voices.name | Where-Object {
      $_.ToUpper() -like "$upper*"
  } | ForEach-Object {
      $_;
  }
}

# requires ffplay (can be installed from ffmpeg)
function Start-GoogleTTS ($text, $speed, $outFile, $voiceName) {
  if ($null -eq $env:GOOGLE_API_KEY_TTS) {
    throw 'You must set the GOOGLE_API_KEY_TTS environment variable to use GoogleTTS.'
  }
  
  $text = $text.Replace('“', "").Replace('”', "");
  $data = [psobject]::new()
  $audioConfig = [psobject]::new();
  $audioConfig | Add-Member -NotePropertyName 'audioEncoding' -NotePropertyValue 'MP3';
  if ($null -ne $speed) {
      $audioConfig | Add-Member -NotePropertyName 'speakingRate' -NotePropertyValue $speed
  }

  if ($null -eq $voiceName) {
      $voiceName = 'en-US-Standard-C';
  }

  $i = 0;
  $j = 0;
  $parts = $text.Split(' ');
  $outFiles = [System.Collections.Generic.List[string]]::new();
  $useTemp = $null -eq $outFile;
  $inputParam = [psobject]::new();
  $inputParam | Add-Member -NotePropertyName 'text' -NotePropertyValue $null;
  $voice = [psobject]::new();
  $voice | Add-Member -NotePropertyName 'languageCode' -NotePropertyValue 'en-US';
  $voice | Add-Member -NotePropertyName 'name' -NotePropertyValue $voiceName;
  $data | Add-Member -NotePropertyName 'audioConfig' -NotePropertyValue $audioConfig;
  $data | Add-Member -NotePropertyName 'input' -NotePropertyValue $inputParam;
  $data | Add-Member -NotePropertyName 'voice' -NotePropertyValue $voice;
  while ($i -lt $text.Length / 5000) {
    $chunk = '';
    while ($j -lt $parts.Length -and ($chunk.Length + $parts[$j].Length + 1) -lt 5000) {
        $chunk += $parts[$j] + ' ';
        $j++;
    }

    $inputParam.text = $chunk;
    $response = Invoke-WebRequest -Uri ($baseUrl + 'text:synthesize?key=' + $env:GOOGLE_API_KEY_TTS) -ContentType 'application/json' -Body (ConvertTo-Json $data) -Method POST;
    $audioData = $response.Content | ConvertFrom-Json;
    $base64 = [System.Convert]::FromBase64String($audioData.audioContent);
    $tempPath = $env:TEMP;
    if ($null -eq $tempPath) {
      $tempPath = "/tmp";
    }

    $tmpFile = "$tempPath/$([System.DateTime]::Now.ToSTring('yyyy-MM-dd-hh-mm-ss-ff')).mp3";
    $writeFile = "$outFile.$i";
    if ($useTemp) {
        $writeFile = $tmpFile;
    }

    [System.IO.File]::WriteAllBytes($writeFile, $base64);
    if ($useTemp) {
        ffplay -nodisp -autoexit -hide_banner -loglevel panic $writeFile > $null;
        Remove-Item $writeFile -Force;
    }
    else {
        # add it to file for concatenation
        $outFiles.Add($writeFile);
        "file '$writeFile'" | Out-File 'concat.txt' -Append -Encoding ascii;
    }

    $i++;
  }

  if (-not $useTemp) {
    ffmpeg -f concat -safe 0 -i concat.txt -c copy $outFile
    $outFiles | ForEach-Object {
        Remove-Item $_ -Force
    }

    Remove-Item concat.txt -Force
  }
}

Register-ArgumentCompleter -CommandName Start-GoogleTTS -ParameterName voiceName -ScriptBlock $completer;