The alexa developer console has a test section with Voice & Tone where you can provide Alex SSML, select a skill locale and hear the audio response.
There is no API available but using Chrome Developer Tools it is possible to create a javascript snippet to generate multiple SSML MP3s.
The only requirement is to add a debug statement in the page source and make a function global.
- Add some SSML and press Play.
- Check the Network tab and select Fetch/XHR with name getTTS.
- Use the Initiator tab to find e.getTextToSpeech and click the link to open the js.
- Pretty print.
- Find and add a debug statement to the line
t.getAasInstance = p("/alexa/console/ask/displays") - Reload the page
- In the console
window.getDefaultAssClient = t.getDefaultInstance
Use ssmlToAudioFile from the code below adding your skillId.
Don't debug or the file picker will not work.
If performing multiple requests with the snippet be sure to wait between requests.
{
const skillId = "amzn1.ask.skill.YOUR_SKILL_ID";
// when sending multiple requests a wait is necessary
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
function wait(){
return delay(1000);
}
// uses the function that made global ( and some of the internal Alexa dev console js)
async function getSSMLBlob(ssml,locale){
var r = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function removePaddingChars(e) {
return 64 === r.indexOf(e.charAt(e.length - 1)) ? e.substring(0, e.length - 1) : e
}
function decode(e, n) {
e = removePaddingChars(e);
var o, i, s, a, l, u, c, d = (e = removePaddingChars(e)).length / 4 * 3, p = 0, f = 0;
for (o = n ? new Uint8Array(n) : new Uint8Array(d),
e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""),
p = 0; p < d; p += 3)
i = r.indexOf(e.charAt(f++)) << 2 | (l = r.indexOf(e.charAt(f++))) >> 4,
s = (15 & l) << 4 | (u = r.indexOf(e.charAt(f++))) >> 2,
a = (3 & u) << 6 | (c = r.indexOf(e.charAt(f++))),
o[p] = i,
64 !== u && (o[p + 1] = s),
64 !== c && (o[p + 2] = a);
return o
}
function decodeAsArrayBuffer(e) {
var n = e.length / 4 * 3
, r = new ArrayBuffer(n);
return decode(e, r)
}
locale = {
value:locale
}
const res = await window.getDefaultAssClient.getTextToSpeech(ssml, "SSML", skillId,locale);
const audioBase64str = res[0];
const decoded = decodeAsArrayBuffer(audioBase64str);
return new Blob([new Uint8Array(decoded)],{
type: "audio/mp3"
});
}
async function writeFile(fileHandle, contents) {
const writable = await fileHandle.createWritable();
await writable.write(contents);
await writable.close();
}
async function getMp3FileHandle(suggestedName) {
const options = {
suggestedName:suggestedName,
types: [
{
description: 'SSML mp3',
accept: {
'audio/mpeg': ['.mp3'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
async function writeMp3(data,suggestedName){
const fileHandle = await getMp3FileHandle(suggestedName);
await writeFile(fileHandle,data);
}
// *** The function to call - be sure to wait if sending multiple requests
// locale corresponds to the drop down - e.g en-GB
async function ssmlToAudioFile(ssml,locale, suggestedName){
const blob = await getSSMLBlob(ssml,locale);
await writeMp3(blob,suggestedName);
}
// helper ssml functions
function speak(contents){
return `<speak>${contents}</speak>`;
}
function language(lang,contents){
return `<lang xml:lang="${lang}">${contents}</lang>`
}
function voice(voiceName, contents){
return `<voice name="${voiceName}">${contents}</voice>`;
}
function voiceInLanguage(voiceName,lang,contents){
return voice(voiceName, language(lang,contents));
}
function amazonDomain(domain,contents){
return `<amazon:domain name="${domain}">${contents}</amazon:domain>`;
}
function conversational(contents){
return amazonDomain("conversational",contents);
}
function news(contents){
return amazonDomain("news",contents);
}
(async () => {
const speech1 = speak("In the default voice of the skill locale.");
await ssmlToAudioFile(speech1,"en-US","filename1");
await wait();
const speech2 = speak(voice("Amy","In Amy's voice"));
await ssmlToAudioFile(speech2,"en-GB","filename2");
})();
}