Created
July 2, 2025 08:19
-
-
Save willwade/9559eaaf3186902166ae8bf5ac62ffd6 to your computer and use it in GitHub Desktop.
A way of testing issue 6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* Issue 6 Reproduction Demo | |
* | |
* This demo reproduces the exact testing scenarios described in issue6.md | |
* to verify that the fixes work correctly across different engines and formats. | |
* | |
* Tests performed: | |
* 1. client.speak() tests with WAV format (SSML, SMD, Plaintext) | |
* 2. client.speak() tests with MP3 format (SSML, SMD, Plaintext) | |
* 3. sound.play() tests with WAV format (SSML, SMD, Plaintext) | |
* 4. sound.play() tests with MP3 format (SSML, SMD, Plaintext) | |
* | |
* Engines tested: Amazon Polly, ElevenLabs, Microsoft Azure, WitAI, SAPI | |
* | |
* Usage: | |
* node examples/issue6-reproduction-demo.js [engine] | |
* | |
* Examples: | |
* node examples/issue6-reproduction-demo.js # Test all engines | |
* node examples/issue6-reproduction-demo.js polly # Test Amazon Polly only | |
* node examples/issue6-reproduction-demo.js azure # Test Microsoft Azure only | |
*/ | |
import { getEngineConfigs, getEngineConfig } from './shared/engine-configs.js'; | |
import path from 'node:path'; | |
import { fileURLToPath } from 'node:url'; | |
import { createRequire } from 'node:module'; | |
import fs from 'node:fs'; | |
// Load environment variables from .env file | |
const require = createRequire(import.meta.url); | |
try { | |
require('./load-env.cjs'); | |
} catch (error) { | |
console.warn('Warning: Could not load environment variables from .env file:', error.message); | |
} | |
const __filename = fileURLToPath(import.meta.url); | |
const __dirname = path.dirname(__filename); | |
const OUTPUT_DIR = path.join(__dirname, "test-output"); | |
// Test texts for different scenarios | |
const TEST_TEXTS = { | |
plaintext: "This is a test of the text to speech engine. Testing one two three.", | |
ssml: "<speak>This is a test of the <emphasis>text to speech</emphasis> engine with <break time='500ms'/> SSML markup.</speak>", | |
smd: "<speak>This is a test of the <prosody rate='slow'>speech markdown</prosody> engine with <break time='1s'/> SMD syntax.</speak>" // Using SSML as SMD equivalent | |
}; | |
// Engines to test (matching issue6.md) | |
const TARGET_ENGINES = ['polly', 'elevenlabs', 'azure', 'witai', 'sapi']; | |
/** | |
* Ensure output directory exists | |
*/ | |
function ensureOutputDir() { | |
if (!fs.existsSync(OUTPUT_DIR)) { | |
fs.mkdirSync(OUTPUT_DIR, { recursive: true }); | |
} | |
} | |
/** | |
* Parse command line arguments | |
*/ | |
function parseArgs() { | |
const args = process.argv.slice(2); | |
if (args.includes('--help') || args.includes('-h')) { | |
return { showHelp: true }; | |
} | |
const engine = args.length > 0 ? args[0].toLowerCase() : null; | |
return { engine, showHelp: false }; | |
} | |
/** | |
* Show help information | |
*/ | |
function showHelp() { | |
console.log('Issue 6 Reproduction Demo'); | |
console.log(''); | |
console.log('Usage: node examples/issue6-reproduction-demo.js [engine]'); | |
console.log(''); | |
console.log('Available engines:'); | |
TARGET_ENGINES.forEach(engine => { | |
console.log(` ${engine}`); | |
}); | |
console.log(''); | |
console.log('Examples:'); | |
console.log(' node examples/issue6-reproduction-demo.js # Test all engines'); | |
console.log(' node examples/issue6-reproduction-demo.js polly # Test Amazon Polly only'); | |
console.log(' node examples/issue6-reproduction-demo.js azure # Test Microsoft Azure only'); | |
} | |
/** | |
* Test client.speak() method with different formats and text types | |
*/ | |
async function testClientSpeak(engineName, client, format) { | |
console.log(`\n--- Testing client.speak() with ${format.toUpperCase()} format ---`); | |
const results = { | |
ssml: 'unknown', | |
smd: 'unknown', | |
plaintext: 'unknown' | |
}; | |
// Test SSML | |
try { | |
console.log(`Testing SSML with ${format}...`); | |
await client.speak(TEST_TEXTS.ssml); | |
results.ssml = 'working'; | |
console.log('✓ SSML test completed successfully'); | |
} catch (error) { | |
results.ssml = `error: ${error.message}`; | |
console.log(`✗ SSML test failed: ${error.message}`); | |
} | |
// Test SMD (using SSML variant) | |
try { | |
console.log(`Testing SMD with ${format}...`); | |
await client.speak(TEST_TEXTS.smd); | |
results.smd = 'working'; | |
console.log('✓ SMD test completed successfully'); | |
} catch (error) { | |
results.smd = `error: ${error.message}`; | |
console.log(`✗ SMD test failed: ${error.message}`); | |
} | |
// Test plaintext | |
try { | |
console.log(`Testing plaintext with ${format}...`); | |
await client.speak(TEST_TEXTS.plaintext); | |
results.plaintext = 'working'; | |
console.log('✓ Plaintext test completed successfully'); | |
} catch (error) { | |
results.plaintext = `error: ${error.message}`; | |
console.log(`✗ Plaintext test failed: ${error.message}`); | |
} | |
return results; | |
} | |
/** | |
* Test sound.play() method by first synthesizing to file, then playing | |
*/ | |
async function testSoundPlay(engineName, client, format) { | |
console.log(`\n--- Testing sound.play() with ${format.toUpperCase()} format ---`); | |
const results = { | |
ssml: 'unknown', | |
smd: 'unknown', | |
plaintext: 'unknown' | |
}; | |
ensureOutputDir(); | |
// Test SSML | |
try { | |
console.log(`Testing SSML playback with ${format}...`); | |
const filename = path.join(OUTPUT_DIR, `${engineName}-ssml-playback.${format}`); | |
await client.synthToFile(TEST_TEXTS.ssml, filename, format); | |
// Now play the file using the new audio input feature | |
await client.speak({ filename: filename }); | |
results.ssml = 'working'; | |
console.log('✓ SSML playback test completed successfully'); | |
} catch (error) { | |
results.ssml = `error: ${error.message}`; | |
console.log(`✗ SSML playback test failed: ${error.message}`); | |
} | |
// Test SMD | |
try { | |
console.log(`Testing SMD playback with ${format}...`); | |
const filename = path.join(OUTPUT_DIR, `${engineName}-smd-playback.${format}`); | |
await client.synthToFile(TEST_TEXTS.smd, filename, format); | |
// Now play the file using the new audio input feature | |
await client.speak({ filename: filename }); | |
results.smd = 'working'; | |
console.log('✓ SMD playback test completed successfully'); | |
} catch (error) { | |
results.smd = `error: ${error.message}`; | |
console.log(`✗ SMD playback test failed: ${error.message}`); | |
} | |
// Test plaintext | |
try { | |
console.log(`Testing plaintext playback with ${format}...`); | |
const filename = path.join(OUTPUT_DIR, `${engineName}-plaintext-playback.${format}`); | |
await client.synthToFile(TEST_TEXTS.plaintext, filename, format); | |
// Now play the file using the new audio input feature | |
await client.speak({ filename: filename }); | |
results.plaintext = 'working'; | |
console.log('✓ Plaintext playback test completed successfully'); | |
} catch (error) { | |
results.plaintext = `error: ${error.message}`; | |
console.log(`✗ Plaintext playback test failed: ${error.message}`); | |
} | |
return results; | |
} | |
/** | |
* Test a single engine with all scenarios | |
*/ | |
async function testEngine(engineName, engineConfig) { | |
console.log(`\n${'='.repeat(60)}`); | |
console.log(`Testing Engine: ${engineName.toUpperCase()}`); | |
console.log(`${'='.repeat(60)}`); | |
let client = null; | |
const results = { | |
'client.speak() WAV': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' }, | |
'client.speak() MP3': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' }, | |
'sound.play() WAV': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' }, | |
'sound.play() MP3': { ssml: 'skipped', smd: 'skipped', plaintext: 'skipped' } | |
}; | |
try { | |
// Initialize client | |
console.log(`Initializing ${engineName}...`); | |
client = engineConfig.factory(); | |
// Check credentials | |
console.log(`Checking credentials for ${engineName}...`); | |
const credentialsValid = await client.checkCredentials(); | |
if (!credentialsValid) { | |
console.log(`❌ Invalid credentials for ${engineName}, skipping tests`); | |
return results; | |
} | |
console.log(`✅ Credentials valid for ${engineName}`); | |
// Get and set voice | |
const voices = await client.getVoices(); | |
if (voices.length === 0) { | |
console.log(`❌ No voices available for ${engineName}, skipping tests`); | |
return results; | |
} | |
// Select appropriate voice for the engine | |
let voice = voices[0]; | |
if (engineName === 'polly') { | |
// For Polly, prefer standard voices that support SSML | |
const standardVoices = ['Geraint', 'Raveena', 'Aditi', 'Ivy', 'Joanna', 'Kendra', 'Matthew', 'Justin']; | |
for (const standardVoice of standardVoices) { | |
const foundVoice = voices.find(v => v.id === standardVoice); | |
if (foundVoice) { | |
voice = foundVoice; | |
break; | |
} | |
} | |
} | |
console.log(`Using voice: ${voice.name} (${voice.id})`); | |
await client.setVoice(voice.id); | |
// Test 1: client.speak() with WAV | |
results['client.speak() WAV'] = await testClientSpeak(engineName, client, 'wav'); | |
// Test 2: client.speak() with MP3 | |
results['client.speak() MP3'] = await testClientSpeak(engineName, client, 'mp3'); | |
// Test 3: sound.play() with WAV | |
results['sound.play() WAV'] = await testSoundPlay(engineName, client, 'wav'); | |
// Test 4: sound.play() with MP3 | |
results['sound.play() MP3'] = await testSoundPlay(engineName, client, 'mp3'); | |
console.log(`\n✅ All tests completed for ${engineName}`); | |
} catch (error) { | |
console.error(`❌ Error testing ${engineName}:`, error.message); | |
} | |
return results; | |
} | |
/** | |
* Print results table | |
*/ | |
function printResultsTable(allResults) { | |
console.log(`\n${'='.repeat(80)}`); | |
console.log('ISSUE 6 REPRODUCTION RESULTS'); | |
console.log(`${'='.repeat(80)}`); | |
const testTypes = ['client.speak() WAV', 'client.speak() MP3', 'sound.play() WAV', 'sound.play() MP3']; | |
const textTypes = ['ssml', 'smd', 'plaintext']; | |
for (const testType of testTypes) { | |
console.log(`\n## ${testType} tests\n`); | |
console.log('| engine | SSML test | SMD test | Plaintext test |'); | |
console.log('|:----------------|:----------------------------------------------|:----------------------------------------------|:---------------------|'); | |
for (const engineName of TARGET_ENGINES) { | |
if (allResults[engineName]) { | |
const results = allResults[engineName][testType]; | |
const ssmlResult = results.ssml === 'working' ? 'working' : (results.ssml.includes('error') ? 'error' : results.ssml); | |
const smdResult = results.smd === 'working' ? 'working' : (results.smd.includes('error') ? 'error' : results.smd); | |
const plaintextResult = results.plaintext === 'working' ? 'working' : (results.plaintext.includes('error') ? 'error' : results.plaintext); | |
console.log(`| ${engineName.padEnd(15)} | ${ssmlResult.padEnd(45)} | ${smdResult.padEnd(45)} | ${plaintextResult.padEnd(20)} |`); | |
} | |
} | |
} | |
} | |
/** | |
* Main function | |
*/ | |
async function main() { | |
const { engine, showHelp } = parseArgs(); | |
if (showHelp) { | |
showHelp(); | |
return; | |
} | |
console.log('Issue 6 Reproduction Demo'); | |
console.log('Testing TTS engines with different formats and playback methods'); | |
console.log(`Target engine: ${engine || 'all'}`); | |
const engineConfigs = await getEngineConfigs(); | |
const allResults = {}; | |
// Determine which engines to test | |
const enginesToTest = engine | |
? (TARGET_ENGINES.includes(engine) ? [engine] : []) | |
: TARGET_ENGINES; | |
if (enginesToTest.length === 0) { | |
console.error(`Unknown engine: ${engine}`); | |
console.log('Available engines:', TARGET_ENGINES.join(', ')); | |
process.exit(1); | |
} | |
// Test each engine | |
for (const engineName of enginesToTest) { | |
const engineConfig = getEngineConfig(engineName, engineConfigs); | |
if (engineConfig) { | |
allResults[engineName] = await testEngine(engineName, engineConfig); | |
} else { | |
console.error(`Engine configuration not found: ${engineName}`); | |
} | |
} | |
// Print results table | |
printResultsTable(allResults); | |
console.log(`\n${'='.repeat(80)}`); | |
console.log('Demo completed! Check the results above to verify issue 6 fixes.'); | |
console.log(`${'='.repeat(80)}`); | |
} | |
// Run the demo | |
main().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment