Skip to content

Commit

Permalink
Add Voiceforge voices (#16)
Browse files Browse the repository at this point in the history
* adds VoiceForge voices

Added 26 functional voices.
Based on my testing, character limit seemed to be arbitrary, always somewhere between 540-550 characters in length. I've set the limit to 540.
One voice resulted in timeouts which has been added to issue #13

* Update footer.php
  • Loading branch information
chrisjp authored Oct 1, 2023
1 parent a045f96 commit a869527
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
33 changes: 33 additions & 0 deletions assets/js/voices.json
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,39 @@
{"vid": "1-7-40", "name": "Vira", "flag": "UA", "lang": "Ukrainian", "accent": "", "gender": "F"}
]
},
"VoiceForge":
{
"charLimit": 540,
"countBytes": false,
"voices": [
{"vid": "Conrad", "name": "Conrad", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Designer", "name": "Designer Dave", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Diesel", "name": "Diesel", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Dog", "name": "Dog", "flag": "US", "lang": "English", "accent": "", "gender": "O", "customEmoji": "\uD83D\uDC15"},
{"vid": "Evilgenius", "name": "Evil Genius", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Frank", "name": "Frank", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "French-fry", "name": "French-fry", "flag": "FR", "lang": "English", "accent": "French", "gender": "M"},
{"vid": "Gregory", "name": "Gregory", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Jerkface", "name": "Jerkface", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "JerseyGirl", "name": "Jersey Girl", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "Kayla", "name": "Kayla", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "Kevin", "name": "Kevin", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Kidaroo", "name": "Kidaroo", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Princess", "name": "Princess", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "RansomNote", "name": "Ransom Note", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Robot", "name": "Robot", "flag": "US", "lang": "English", "accent": "American", "gender": "O", "customEmoji": "\uD83E\uDD16"},
{"vid": "Shygirl", "name": "Shy Girl", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "Susan", "name": "Susan", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "Tamika", "name": "Tamika", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "TopHat", "name": "Top Hat", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Vixen", "name": "Vixen", "flag": "US", "lang": "English", "accent": "American", "gender": "F"},
{"vid": "Vlad", "name": "Vlad", "flag": "RU", "lang": "English", "accent": "Russian", "gender": "M"},
{"vid": "Warren", "name": "Warren Peas", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Wiseguy", "name": "Wiseguy", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Zach", "name": "Zach", "flag": "US", "lang": "English", "accent": "American", "gender": "M"},
{"vid": "Obama", "name": "Obama", "flag": "US", "lang": "English", "accent": "American", "gender": "O", "customEmoji": "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB2"}
]
},
"Bing Translator":
{
"charLimit": 3000,
Expand Down
1 change: 1 addition & 0 deletions include/footer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// <a href="https://www.ibm.com/cloud/watson-text-to-speech/">IBM Watson</a>
// <a href="https://www.acapela-group.com/demos/">Acapela</a>
// <a href="https://www.oddcast.com/ttsdemo/index.php">Oddcast</a>
// <a href="https://www.voiceforge.com/">VoiceForge</a>
// <a href="https://learn.microsoft.com/en-us/azure/cognitive-services/Speech-Service/">Microsoft Azure Speech</a> (via <a href="https://www.bing.com/translator">Bing Translator</a>)
// <a href="https://translate.google.com/">Google Translate</a>
</p>
Expand Down
140 changes: 140 additions & 0 deletions src/Services/VoiceForge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace ChrisJP\TTS\Services;

use ChrisJP\TTS\Services\Service;
use ChrisJP\TTS\Request;
use ChrisJP\TTS\ReturnObjectTrait;

/**
* VoiceForge
*
* An app exclusively available on iOS
* Uses very simple GET requests to return audio in WAV format
*/
class VoiceForge implements Service
{

use ReturnObjectTrait;

const baseURL = 'https://api.voiceforge.com/swift_engine?';

const demoSite = '';

/**
* Full name of this service.
*
* @var string
*/
private string $name = 'VoiceForge';

/**
* Short name of this service.
* Will be used in audio filenames so keep it short and no weird characters/spaces etc.
*
* @var string
*/
private string $shortName = 'VoiceForge';

/**
* The default voice that will be used if one is not set.
*
* @var string
*/
private string $defaultVoice = 'Conrad';

/**
* Returns the full name of this service.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}

/**
* Returns the short name of this service.
*
* @return string
*/
public function getShortName(): string
{
return $this->shortName;
}

/**
* Returns the default voice ID to be used with this service.
*
* @return string
*/
public function getDefaultVoice(): string
{
return $this->defaultVoice;
}

/**
* Constructs the data required to be sent to the API and calls sendRequest()
* Returns an object containing the audio URL and various other data.
*
* @param string $voice
* @param string $text
* @return object
*/
public function requestTTS(string $voice, string $text): object
{
$params = [
'voice' => $voice,
'msg' => $text,
'email' => 'null', // email param is required but doesn't have to be valid.
];

// HTTP_X_API_KEY header is required
// hardcoding this value for now as unsure how to reliably obtain
// borrowed from https://github.com/Wrapper-Offline/Wrapper-Offline
$headers = [
'HTTP_X_API_KEY: 8b3f76a8539',
];

$request = new Request($this::baseURL);
$request->sendRequest($params, false, $headers);

$response = $request->getResponse();
$curlInfo = $request->getInfo();

return $this->handleResponse($response, $voice, $text, $curlInfo);
}

/**
* Handles the response we got from the cURL request made in sendRequest()
* Creates and returns an object containing the audio URL and any other data we require.
*
* @param $response
* @param string $voice
* @param string $text
* @param array $info
* @return object
*/
private function handleResponse($response, string $voice, string $text, array $curlInfo = []): object
{
$success = false;
$audioUrl = null;
$errorMessage = null;

if ($curlInfo['http_code'] === 200 && $curlInfo['content_type'] === 'audio/wav') {
// TODO: Convert to MP3
$success = true;

// $response is raw WAV audio data, so we can provide a data URI for it
$audioUrl ='data:audio/wav;base64,' . base64_encode($response);
}
else {
$errorMessage = 'HTTP Error ' . $curlInfo['http_code'] . '; Attempted URL: ' . $curlInfo['url'];
}

// No point returning the MP3 data twice so pass null instead of $response as the final param
$returnData = $this->buildReturnObject($success, $audioUrl, null, $curlInfo, $errorMessage, null);

return $returnData;
}
}

0 comments on commit a869527

Please sign in to comment.