Housekeeping: Remove unnecessary test and temporary files
- Removed test scripts (test_*.py, tts-debug-script.py) - Removed test output files (tts_test_output.mp3, test-cors.html) - Removed redundant static/js/app.js (using TypeScript dist/ instead) - Removed outdated setup-script.sh - Removed Python cache directory (__pycache__) - Removed Claude IDE local settings (.claude/) - Updated .gitignore with better patterns for: - Test files - Debug scripts - Claude IDE settings - Standalone compiled JS This cleanup reduces repository size and removes temporary/debug files that shouldn't be version controlled. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bcbac5c8b3
commit
b5f2b53262
12
.gitignore
vendored
12
.gitignore
vendored
@ -67,3 +67,15 @@ vapid_public.pem
|
|||||||
.master_key
|
.master_key
|
||||||
secrets.db
|
secrets.db
|
||||||
*.key
|
*.key
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
test_*.py
|
||||||
|
*_test_output.*
|
||||||
|
test-*.html
|
||||||
|
*-debug-script.py
|
||||||
|
|
||||||
|
# Claude IDE
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# Standalone compiled JS (use dist/ instead)
|
||||||
|
static/js/app.js
|
||||||
|
776
setup-script.sh
776
setup-script.sh
@ -1,776 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Create necessary directories
|
|
||||||
mkdir -p templates static/{css,js}
|
|
||||||
|
|
||||||
# Move HTML template to templates directory
|
|
||||||
cat > templates/index.html << 'EOL'
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Voice Language Translator</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding-top: 20px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
.record-btn {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 32px;
|
|
||||||
margin: 20px auto;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
.record-btn:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.recording {
|
|
||||||
background-color: #dc3545 !important;
|
|
||||||
animation: pulse 1.5s infinite;
|
|
||||||
}
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.card-header {
|
|
||||||
border-radius: 15px 15px 0 0 !important;
|
|
||||||
}
|
|
||||||
.language-select {
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.text-display {
|
|
||||||
min-height: 100px;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.btn-action {
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 8px 15px;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
.spinner-border {
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
.status-indicator {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-style: italic;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="text-center mb-4">Voice Language Translator</h1>
|
|
||||||
<p class="text-center text-muted">Powered by Gemma 3, Whisper & Edge TTS</p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header bg-primary text-white">
|
|
||||||
<h5 class="mb-0">Source</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<select id="sourceLanguage" class="form-select language-select mb-3">
|
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language }}">{{ language }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<div class="text-display" id="sourceText">
|
|
||||||
<p class="text-muted">Your transcribed text will appear here...</p>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button id="playSource" class="btn btn-outline-primary btn-action" disabled>
|
|
||||||
<i class="fas fa-play"></i> Play
|
|
||||||
</button>
|
|
||||||
<button id="clearSource" class="btn btn-outline-secondary btn-action">
|
|
||||||
<i class="fas fa-trash"></i> Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header bg-success text-white">
|
|
||||||
<h5 class="mb-0">Translation</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<select id="targetLanguage" class="form-select language-select mb-3">
|
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language }}">{{ language }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<div class="text-display" id="translatedText">
|
|
||||||
<p class="text-muted">Translation will appear here...</p>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button id="playTranslation" class="btn btn-outline-success btn-action" disabled>
|
|
||||||
<i class="fas fa-play"></i> Play
|
|
||||||
</button>
|
|
||||||
<button id="clearTranslation" class="btn btn-outline-secondary btn-action">
|
|
||||||
<i class="fas fa-trash"></i> Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<button id="recordBtn" class="btn btn-primary record-btn">
|
|
||||||
<i class="fas fa-microphone"></i>
|
|
||||||
</button>
|
|
||||||
<p class="status-indicator" id="statusIndicator">Click to start recording</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<button id="translateBtn" class="btn btn-success" disabled>
|
|
||||||
<i class="fas fa-language"></i> Translate
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<div class="progress d-none" id="progressContainer">
|
|
||||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<audio id="audioPlayer" style="display: none;"></audio>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// DOM elements
|
|
||||||
const recordBtn = document.getElementById('recordBtn');
|
|
||||||
const translateBtn = document.getElementById('translateBtn');
|
|
||||||
const sourceText = document.getElementById('sourceText');
|
|
||||||
const translatedText = document.getElementById('translatedText');
|
|
||||||
const sourceLanguage = document.getElementById('sourceLanguage');
|
|
||||||
const targetLanguage = document.getElementById('targetLanguage');
|
|
||||||
const playSource = document.getElementById('playSource');
|
|
||||||
const playTranslation = document.getElementById('playTranslation');
|
|
||||||
const clearSource = document.getElementById('clearSource');
|
|
||||||
const clearTranslation = document.getElementById('clearTranslation');
|
|
||||||
const statusIndicator = document.getElementById('statusIndicator');
|
|
||||||
const progressContainer = document.getElementById('progressContainer');
|
|
||||||
const progressBar = document.getElementById('progressBar');
|
|
||||||
const audioPlayer = document.getElementById('audioPlayer');
|
|
||||||
|
|
||||||
// Set initial values
|
|
||||||
let isRecording = false;
|
|
||||||
let mediaRecorder = null;
|
|
||||||
let audioChunks = [];
|
|
||||||
let currentSourceText = '';
|
|
||||||
let currentTranslationText = '';
|
|
||||||
|
|
||||||
// Make sure target language is different from source
|
|
||||||
if (targetLanguage.options[0].value === sourceLanguage.value) {
|
|
||||||
targetLanguage.selectedIndex = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners for language selection
|
|
||||||
sourceLanguage.addEventListener('change', function() {
|
|
||||||
if (targetLanguage.value === sourceLanguage.value) {
|
|
||||||
for (let i = 0; i < targetLanguage.options.length; i++) {
|
|
||||||
if (targetLanguage.options[i].value !== sourceLanguage.value) {
|
|
||||||
targetLanguage.selectedIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
targetLanguage.addEventListener('change', function() {
|
|
||||||
if (targetLanguage.value === sourceLanguage.value) {
|
|
||||||
for (let i = 0; i < sourceLanguage.options.length; i++) {
|
|
||||||
if (sourceLanguage.options[i].value !== targetLanguage.value) {
|
|
||||||
sourceLanguage.selectedIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Record button click event
|
|
||||||
recordBtn.addEventListener('click', function() {
|
|
||||||
if (isRecording) {
|
|
||||||
stopRecording();
|
|
||||||
} else {
|
|
||||||
startRecording();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to start recording
|
|
||||||
function startRecording() {
|
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
||||||
.then(stream => {
|
|
||||||
mediaRecorder = new MediaRecorder(stream);
|
|
||||||
audioChunks = [];
|
|
||||||
|
|
||||||
mediaRecorder.addEventListener('dataavailable', event => {
|
|
||||||
audioChunks.push(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaRecorder.addEventListener('stop', () => {
|
|
||||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
|
||||||
transcribeAudio(audioBlob);
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaRecorder.start();
|
|
||||||
isRecording = true;
|
|
||||||
recordBtn.classList.add('recording');
|
|
||||||
recordBtn.classList.replace('btn-primary', 'btn-danger');
|
|
||||||
recordBtn.innerHTML = '<i class="fas fa-stop"></i>';
|
|
||||||
statusIndicator.textContent = 'Recording... Click to stop';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error accessing microphone:', error);
|
|
||||||
alert('Error accessing microphone. Please make sure you have given permission for microphone access.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to stop recording
|
|
||||||
function stopRecording() {
|
|
||||||
mediaRecorder.stop();
|
|
||||||
isRecording = false;
|
|
||||||
recordBtn.classList.remove('recording');
|
|
||||||
recordBtn.classList.replace('btn-danger', 'btn-primary');
|
|
||||||
recordBtn.innerHTML = '<i class="fas fa-microphone"></i>';
|
|
||||||
statusIndicator.textContent = 'Processing audio...';
|
|
||||||
|
|
||||||
// Stop all audio tracks
|
|
||||||
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to transcribe audio
|
|
||||||
function transcribeAudio(audioBlob) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('audio', audioBlob);
|
|
||||||
formData.append('source_lang', sourceLanguage.value);
|
|
||||||
|
|
||||||
showProgress();
|
|
||||||
|
|
||||||
fetch('/transcribe', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
hideProgress();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
currentSourceText = data.text;
|
|
||||||
sourceText.innerHTML = `<p>${data.text}</p>`;
|
|
||||||
playSource.disabled = false;
|
|
||||||
translateBtn.disabled = false;
|
|
||||||
statusIndicator.textContent = 'Transcription complete';
|
|
||||||
} else {
|
|
||||||
sourceText.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
|
|
||||||
statusIndicator.textContent = 'Transcription failed';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
hideProgress();
|
|
||||||
console.error('Transcription error:', error);
|
|
||||||
sourceText.innerHTML = `<p class="text-danger">Failed to transcribe audio. Please try again.</p>`;
|
|
||||||
statusIndicator.textContent = 'Transcription failed';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate button click event
|
|
||||||
translateBtn.addEventListener('click', function() {
|
|
||||||
if (!currentSourceText) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
statusIndicator.textContent = 'Translating...';
|
|
||||||
showProgress();
|
|
||||||
|
|
||||||
fetch('/translate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
text: currentSourceText,
|
|
||||||
source_lang: sourceLanguage.value,
|
|
||||||
target_lang: targetLanguage.value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
hideProgress();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
currentTranslationText = data.translation;
|
|
||||||
translatedText.innerHTML = `<p>${data.translation}</p>`;
|
|
||||||
playTranslation.disabled = false;
|
|
||||||
statusIndicator.textContent = 'Translation complete';
|
|
||||||
} else {
|
|
||||||
translatedText.innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
|
|
||||||
statusIndicator.textContent = 'Translation failed';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
hideProgress();
|
|
||||||
console.error('Translation error:', error);
|
|
||||||
translatedText.innerHTML = `<p class="text-danger">Failed to translate. Please try again.</p>`;
|
|
||||||
statusIndicator.textContent = 'Translation failed';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Play source text
|
|
||||||
playSource.addEventListener('click', function() {
|
|
||||||
if (!currentSourceText) return;
|
|
||||||
|
|
||||||
playAudio(currentSourceText, sourceLanguage.value);
|
|
||||||
statusIndicator.textContent = 'Playing source audio...';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Play translation
|
|
||||||
playTranslation.addEventListener('click', function() {
|
|
||||||
if (!currentTranslationText) return;
|
|
||||||
|
|
||||||
playAudio(currentTranslationText, targetLanguage.value);
|
|
||||||
statusIndicator.textContent = 'Playing translation audio...';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to play audio via TTS
|
|
||||||
function playAudio(text, language) {
|
|
||||||
showProgress();
|
|
||||||
|
|
||||||
fetch('/speak', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
text: text,
|
|
||||||
language: language
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
hideProgress();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
audioPlayer.src = data.audio_url;
|
|
||||||
audioPlayer.onended = function() {
|
|
||||||
statusIndicator.textContent = 'Ready';
|
|
||||||
};
|
|
||||||
audioPlayer.play();
|
|
||||||
} else {
|
|
||||||
statusIndicator.textContent = 'TTS failed';
|
|
||||||
alert('Failed to play audio: ' + data.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
hideProgress();
|
|
||||||
console.error('TTS error:', error);
|
|
||||||
statusIndicator.textContent = 'TTS failed';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear buttons
|
|
||||||
clearSource.addEventListener('click', function() {
|
|
||||||
sourceText.innerHTML = '<p class="text-muted">Your transcribed text will appear here...</p>';
|
|
||||||
currentSourceText = '';
|
|
||||||
playSource.disabled = true;
|
|
||||||
translateBtn.disabled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTranslation.addEventListener('click', function() {
|
|
||||||
translatedText.innerHTML = '<p class="text-muted">Translation will appear here...</p>';
|
|
||||||
currentTranslationText = '';
|
|
||||||
playTranslation.disabled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Progress indicator functions
|
|
||||||
function showProgress() {
|
|
||||||
progressContainer.classList.remove('d-none');
|
|
||||||
let progress = 0;
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
progress += 5;
|
|
||||||
if (progress > 90) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
progressBar.style.width = `${progress}%`;
|
|
||||||
}, 100);
|
|
||||||
progressBar.dataset.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideProgress() {
|
|
||||||
const interval = progressBar.dataset.interval;
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(Number(interval));
|
|
||||||
}
|
|
||||||
progressBar.style.width = '100%';
|
|
||||||
setTimeout(() => {
|
|
||||||
progressContainer.classList.add('d-none');
|
|
||||||
progressBar.style.width = '0%';
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create app.py
|
|
||||||
cat > app.py << 'EOL'
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import tempfile
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
from flask import Flask, render_template, request, jsonify, Response, send_file
|
|
||||||
import whisper
|
|
||||||
import torch
|
|
||||||
import ollama
|
|
||||||
import logging
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp()
|
|
||||||
app.config['TTS_SERVER'] = os.environ.get('TTS_SERVER_URL', 'http://localhost:5050/v1/audio/speech')
|
|
||||||
app.config['TTS_API_KEY'] = os.environ.get('TTS_API_KEY', 'your_api_key_here')
|
|
||||||
|
|
||||||
# Add a route to check TTS server status
|
|
||||||
@app.route('/check_tts_server', methods=['GET'])
|
|
||||||
def check_tts_server():
|
|
||||||
try:
|
|
||||||
# Try a simple HTTP request to the TTS server
|
|
||||||
response = requests.get(app.config['TTS_SERVER'].rsplit('/api/generate', 1)[0] + '/status', timeout=5)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'online',
|
|
||||||
'url': app.config['TTS_SERVER']
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': f'TTS server returned status code {response.status_code}',
|
|
||||||
'url': app.config['TTS_SERVER']
|
|
||||||
})
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': f'Cannot connect to TTS server: {str(e)}',
|
|
||||||
'url': app.config['TTS_SERVER']
|
|
||||||
})
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Load Whisper model
|
|
||||||
logger.info("Loading Whisper model...")
|
|
||||||
whisper_model = whisper.load_model("base")
|
|
||||||
logger.info("Whisper model loaded successfully")
|
|
||||||
|
|
||||||
# Supported languages
|
|
||||||
SUPPORTED_LANGUAGES = {
|
|
||||||
"ar": "Arabic",
|
|
||||||
"hy": "Armenian",
|
|
||||||
"az": "Azerbaijani",
|
|
||||||
"en": "English",
|
|
||||||
"fr": "French",
|
|
||||||
"ka": "Georgian",
|
|
||||||
"kk": "Kazakh",
|
|
||||||
"zh": "Mandarin",
|
|
||||||
"fa": "Farsi",
|
|
||||||
"pt": "Portuguese",
|
|
||||||
"ru": "Russian",
|
|
||||||
"es": "Spanish",
|
|
||||||
"tr": "Turkish",
|
|
||||||
"uz": "Uzbek"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map language names to language codes
|
|
||||||
LANGUAGE_TO_CODE = {v: k for k, v in SUPPORTED_LANGUAGES.items()}
|
|
||||||
|
|
||||||
# Map language names to OpenAI TTS voice options
|
|
||||||
LANGUAGE_TO_VOICE = {
|
|
||||||
"Arabic": "alloy", # Using OpenAI general voices
|
|
||||||
"Armenian": "echo", # as OpenAI doesn't have specific voices
|
|
||||||
"Azerbaijani": "nova", # for all these languages
|
|
||||||
"English": "echo", # We'll use the available voices
|
|
||||||
"French": "alloy", # and rely on the translation being
|
|
||||||
"Georgian": "fable", # in the correct language text
|
|
||||||
"Kazakh": "onyx",
|
|
||||||
"Mandarin": "shimmer",
|
|
||||||
"Farsi": "nova",
|
|
||||||
"Portuguese": "alloy",
|
|
||||||
"Russian": "echo",
|
|
||||||
"Spanish": "nova",
|
|
||||||
"Turkish": "fable",
|
|
||||||
"Uzbek": "onyx"
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return render_template('index.html', languages=sorted(SUPPORTED_LANGUAGES.values()))
|
|
||||||
|
|
||||||
@app.route('/transcribe', methods=['POST'])
|
|
||||||
def transcribe():
|
|
||||||
if 'audio' not in request.files:
|
|
||||||
return jsonify({'error': 'No audio file provided'}), 400
|
|
||||||
|
|
||||||
audio_file = request.files['audio']
|
|
||||||
source_lang = request.form.get('source_lang', '')
|
|
||||||
|
|
||||||
# Save the audio file temporarily
|
|
||||||
temp_path = os.path.join(app.config['UPLOAD_FOLDER'], 'input_audio.wav')
|
|
||||||
audio_file.save(temp_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Use Whisper for transcription
|
|
||||||
result = whisper_model.transcribe(
|
|
||||||
temp_path,
|
|
||||||
language=LANGUAGE_TO_CODE.get(source_lang, None)
|
|
||||||
)
|
|
||||||
transcribed_text = result["text"]
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'text': transcribed_text
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Transcription error: {str(e)}")
|
|
||||||
return jsonify({'error': f'Transcription failed: {str(e)}'}), 500
|
|
||||||
finally:
|
|
||||||
# Clean up the temporary file
|
|
||||||
if os.path.exists(temp_path):
|
|
||||||
os.remove(temp_path)
|
|
||||||
|
|
||||||
@app.route('/translate', methods=['POST'])
|
|
||||||
def translate():
|
|
||||||
try:
|
|
||||||
data = request.json
|
|
||||||
text = data.get('text', '')
|
|
||||||
source_lang = data.get('source_lang', '')
|
|
||||||
target_lang = data.get('target_lang', '')
|
|
||||||
|
|
||||||
if not text or not source_lang or not target_lang:
|
|
||||||
return jsonify({'error': 'Missing required parameters'}), 400
|
|
||||||
|
|
||||||
# Create a prompt for Gemma 3 translation
|
|
||||||
prompt = f"""
|
|
||||||
Translate the following text from {source_lang} to {target_lang}:
|
|
||||||
|
|
||||||
"{text}"
|
|
||||||
|
|
||||||
Provide only the translation without any additional text.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Use Ollama to interact with Gemma 3
|
|
||||||
response = ollama.chat(
|
|
||||||
model="gemma3",
|
|
||||||
messages=[
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": prompt
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
translated_text = response['message']['content'].strip()
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'translation': translated_text
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Translation error: {str(e)}")
|
|
||||||
return jsonify({'error': f'Translation failed: {str(e)}'}), 500
|
|
||||||
|
|
||||||
@app.route('/speak', methods=['POST'])
|
|
||||||
def speak():
|
|
||||||
try:
|
|
||||||
data = request.json
|
|
||||||
text = data.get('text', '')
|
|
||||||
language = data.get('language', '')
|
|
||||||
|
|
||||||
if not text or not language:
|
|
||||||
return jsonify({'error': 'Missing required parameters'}), 400
|
|
||||||
|
|
||||||
voice = LANGUAGE_TO_VOICE.get(language)
|
|
||||||
if not voice:
|
|
||||||
return jsonify({'error': 'Unsupported language for TTS'}), 400
|
|
||||||
|
|
||||||
# Get TTS server URL from environment or config
|
|
||||||
tts_server_url = app.config['TTS_SERVER']
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Request TTS from the Edge TTS server
|
|
||||||
logger.info(f"Sending TTS request to {tts_server_url}")
|
|
||||||
tts_response = requests.post(
|
|
||||||
tts_server_url,
|
|
||||||
json={
|
|
||||||
'text': text,
|
|
||||||
'voice': voice,
|
|
||||||
'output_format': 'mp3'
|
|
||||||
},
|
|
||||||
timeout=10 # Add timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"TTS response status: {tts_response.status_code}")
|
|
||||||
|
|
||||||
if tts_response.status_code != 200:
|
|
||||||
error_msg = f'TTS request failed with status {tts_response.status_code}'
|
|
||||||
logger.error(error_msg)
|
|
||||||
|
|
||||||
# Try to get error details from response if possible
|
|
||||||
try:
|
|
||||||
error_details = tts_response.json()
|
|
||||||
logger.error(f"Error details: {error_details}")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return jsonify({'error': error_msg}), 500
|
|
||||||
|
|
||||||
# The response contains the audio data directly
|
|
||||||
temp_audio_path = os.path.join(app.config['UPLOAD_FOLDER'], f'output_{int(time.time())}.mp3')
|
|
||||||
with open(temp_audio_path, 'wb') as f:
|
|
||||||
f.write(tts_response.content)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'audio_url': f'/get_audio/{os.path.basename(temp_audio_path)}'
|
|
||||||
})
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
error_msg = f'Failed to connect to TTS server: {str(e)}'
|
|
||||||
logger.error(error_msg)
|
|
||||||
return jsonify({'error': error_msg}), 500
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"TTS error: {str(e)}")
|
|
||||||
return jsonify({'error': f'TTS failed: {str(e)}'}), 500
|
|
||||||
|
|
||||||
@app.route('/get_audio/<filename>')
|
|
||||||
def get_audio(filename):
|
|
||||||
try:
|
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
||||||
return send_file(file_path, mimetype='audio/mpeg')
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Audio retrieval error: {str(e)}")
|
|
||||||
return jsonify({'error': f'Audio retrieval failed: {str(e)}'}), 500
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create requirements.txt
|
|
||||||
cat > requirements.txt << 'EOL'
|
|
||||||
flask==2.3.2
|
|
||||||
requests==2.31.0
|
|
||||||
openai-whisper==20231117
|
|
||||||
torch==2.1.0
|
|
||||||
ollama==0.1.5
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create README.md
|
|
||||||
cat > README.md << 'EOL'
|
|
||||||
# Voice Language Translator
|
|
||||||
|
|
||||||
A mobile-friendly web application that translates spoken language between multiple languages using:
|
|
||||||
- Gemma 3 open-source LLM via Ollama for translation
|
|
||||||
- OpenAI Whisper for speech-to-text
|
|
||||||
- OpenAI Edge TTS for text-to-speech
|
|
||||||
|
|
||||||
## Supported Languages
|
|
||||||
|
|
||||||
- Arabic
|
|
||||||
- Armenian
|
|
||||||
- Azerbaijani
|
|
||||||
- English
|
|
||||||
- French
|
|
||||||
- Georgian
|
|
||||||
- Kazakh
|
|
||||||
- Mandarin
|
|
||||||
- Farsi
|
|
||||||
- Portuguese
|
|
||||||
- Russian
|
|
||||||
- Spanish
|
|
||||||
- Turkish
|
|
||||||
- Uzbek
|
|
||||||
|
|
||||||
## Setup Instructions
|
|
||||||
|
|
||||||
1. Install the required Python packages:
|
|
||||||
```
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Make sure you have Ollama installed and the Gemma 3 model loaded:
|
|
||||||
```
|
|
||||||
ollama pull gemma3
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Ensure your OpenAI Edge TTS server is running on port 5050.
|
|
||||||
|
|
||||||
4. Run the application:
|
|
||||||
```
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Open your browser and navigate to:
|
|
||||||
```
|
|
||||||
http://localhost:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
1. Select your source language from the dropdown menu
|
|
||||||
2. Press the microphone button and speak
|
|
||||||
3. Press the button again to stop recording
|
|
||||||
4. Wait for the transcription to complete
|
|
||||||
5. Select your target language
|
|
||||||
6. Press the "Translate" button
|
|
||||||
7. Use the play buttons to hear the original or translated text
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
- The app uses Flask for the web server
|
|
||||||
- Audio is processed client-side using the MediaRecorder API
|
|
||||||
- Whisper for speech recognition with language hints
|
|
||||||
- Ollama provides access to the Gemma 3 model for translation
|
|
||||||
- OpenAI Edge TTS delivers natural-sounding speech output
|
|
||||||
|
|
||||||
## Mobile Support
|
|
||||||
|
|
||||||
The interface is fully responsive and designed to work well on mobile devices.
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Make the script executable
|
|
||||||
chmod +x app.py
|
|
||||||
|
|
||||||
echo "Setup complete! Run the app with: python app.py"
|
|
1385
static/js/app.js
1385
static/js/app.js
File diff suppressed because it is too large
Load Diff
228
test-cors.html
228
test-cors.html
@ -1,228 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>CORS Test for Talk2Me</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 50px auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.test-result {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
background-color: #d4edda;
|
|
||||||
color: #155724;
|
|
||||||
border: 1px solid #c3e6cb;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
background-color: #f8d7da;
|
|
||||||
color: #721c24;
|
|
||||||
border: 1px solid #f5c6cb;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
#results {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>CORS Test for Talk2Me API</h1>
|
|
||||||
|
|
||||||
<p>This page tests CORS configuration for the Talk2Me API. Open this file from a different origin (e.g., file:// or a different port) to test cross-origin requests.</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="apiUrl">API Base URL:</label>
|
|
||||||
<input type="text" id="apiUrl" placeholder="http://localhost:5005" value="http://localhost:5005">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Tests:</h2>
|
|
||||||
|
|
||||||
<button onclick="testHealthEndpoint()">Test Health Endpoint</button>
|
|
||||||
<button onclick="testPreflightRequest()">Test Preflight Request</button>
|
|
||||||
<button onclick="testTranscribeEndpoint()">Test Transcribe Endpoint (OPTIONS)</button>
|
|
||||||
<button onclick="testWithCredentials()">Test With Credentials</button>
|
|
||||||
|
|
||||||
<div id="results"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function addResult(test, success, message, details = null) {
|
|
||||||
const resultsDiv = document.getElementById('results');
|
|
||||||
const resultDiv = document.createElement('div');
|
|
||||||
resultDiv.className = `test-result ${success ? 'success' : 'error'}`;
|
|
||||||
|
|
||||||
let html = `<strong>${test}:</strong> ${message}`;
|
|
||||||
if (details) {
|
|
||||||
html += `<pre>${JSON.stringify(details, null, 2)}</pre>`;
|
|
||||||
}
|
|
||||||
resultDiv.innerHTML = html;
|
|
||||||
resultsDiv.appendChild(resultDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApiUrl() {
|
|
||||||
return document.getElementById('apiUrl').value.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testHealthEndpoint() {
|
|
||||||
const apiUrl = getApiUrl();
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/health`, {
|
|
||||||
method: 'GET',
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Origin': window.location.origin
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Check CORS headers
|
|
||||||
const corsHeaders = {
|
|
||||||
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
|
||||||
'Access-Control-Allow-Credentials': response.headers.get('Access-Control-Allow-Credentials')
|
|
||||||
};
|
|
||||||
|
|
||||||
addResult('Health Endpoint GET', true, 'Request successful', {
|
|
||||||
status: response.status,
|
|
||||||
data: data,
|
|
||||||
corsHeaders: corsHeaders
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
addResult('Health Endpoint GET', false, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testPreflightRequest() {
|
|
||||||
const apiUrl = getApiUrl();
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/api/push-public-key`, {
|
|
||||||
method: 'OPTIONS',
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Origin': window.location.origin,
|
|
||||||
'Access-Control-Request-Method': 'GET',
|
|
||||||
'Access-Control-Request-Headers': 'content-type'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const corsHeaders = {
|
|
||||||
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
|
||||||
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
|
|
||||||
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
|
|
||||||
'Access-Control-Max-Age': response.headers.get('Access-Control-Max-Age')
|
|
||||||
};
|
|
||||||
|
|
||||||
addResult('Preflight Request', response.ok, `Status: ${response.status}`, corsHeaders);
|
|
||||||
} catch (error) {
|
|
||||||
addResult('Preflight Request', false, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testTranscribeEndpoint() {
|
|
||||||
const apiUrl = getApiUrl();
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/transcribe`, {
|
|
||||||
method: 'OPTIONS',
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Origin': window.location.origin,
|
|
||||||
'Access-Control-Request-Method': 'POST',
|
|
||||||
'Access-Control-Request-Headers': 'content-type'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const corsHeaders = {
|
|
||||||
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
|
||||||
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
|
|
||||||
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
|
|
||||||
'Access-Control-Allow-Credentials': response.headers.get('Access-Control-Allow-Credentials')
|
|
||||||
};
|
|
||||||
|
|
||||||
addResult('Transcribe Endpoint OPTIONS', response.ok, `Status: ${response.status}`, corsHeaders);
|
|
||||||
} catch (error) {
|
|
||||||
addResult('Transcribe Endpoint OPTIONS', false, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testWithCredentials() {
|
|
||||||
const apiUrl = getApiUrl();
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/health`, {
|
|
||||||
method: 'GET',
|
|
||||||
mode: 'cors',
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Origin': window.location.origin
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
addResult('Request with Credentials', true, 'Request successful', {
|
|
||||||
status: response.status,
|
|
||||||
credentialsIncluded: true,
|
|
||||||
data: data
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
addResult('Request with Credentials', false, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear results before running new tests
|
|
||||||
function clearResults() {
|
|
||||||
document.getElementById('results').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add event listeners
|
|
||||||
document.querySelectorAll('button').forEach(button => {
|
|
||||||
button.addEventListener('click', (e) => {
|
|
||||||
if (!e.target.textContent.includes('Test')) return;
|
|
||||||
clearResults();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show current origin
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
const info = document.createElement('div');
|
|
||||||
info.style.marginBottom = '20px';
|
|
||||||
info.style.padding = '10px';
|
|
||||||
info.style.backgroundColor = '#e9ecef';
|
|
||||||
info.style.borderRadius = '5px';
|
|
||||||
info.innerHTML = `<strong>Current Origin:</strong> ${window.location.origin}<br>
|
|
||||||
<strong>Protocol:</strong> ${window.location.protocol}<br>
|
|
||||||
<strong>Note:</strong> For effective CORS testing, open this file from a different origin than your API server.`;
|
|
||||||
document.body.insertBefore(info, document.querySelector('h2'));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,168 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test script for error logging system
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
from error_logger import ErrorLogger, log_errors, log_performance, get_logger
|
|
||||||
|
|
||||||
def test_basic_logging():
|
|
||||||
"""Test basic logging functionality"""
|
|
||||||
print("\n=== Testing Basic Logging ===")
|
|
||||||
|
|
||||||
# Get logger
|
|
||||||
logger = get_logger('test')
|
|
||||||
|
|
||||||
# Test different log levels
|
|
||||||
logger.debug("This is a debug message")
|
|
||||||
logger.info("This is an info message")
|
|
||||||
logger.warning("This is a warning message")
|
|
||||||
logger.error("This is an error message")
|
|
||||||
|
|
||||||
print("✓ Basic logging test completed")
|
|
||||||
|
|
||||||
def test_error_logging():
|
|
||||||
"""Test error logging with exceptions"""
|
|
||||||
print("\n=== Testing Error Logging ===")
|
|
||||||
|
|
||||||
@log_errors('test.functions')
|
|
||||||
def failing_function():
|
|
||||||
raise ValueError("This is a test error")
|
|
||||||
|
|
||||||
try:
|
|
||||||
failing_function()
|
|
||||||
except ValueError:
|
|
||||||
print("✓ Error was logged")
|
|
||||||
|
|
||||||
# Check if error log exists
|
|
||||||
if os.path.exists('logs/errors.log'):
|
|
||||||
print("✓ Error log file created")
|
|
||||||
|
|
||||||
# Read last line
|
|
||||||
with open('logs/errors.log', 'r') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
if lines:
|
|
||||||
try:
|
|
||||||
error_entry = json.loads(lines[-1])
|
|
||||||
print(f"✓ Error logged with level: {error_entry.get('level')}")
|
|
||||||
print(f"✓ Error type: {error_entry.get('exception', {}).get('type')}")
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("✗ Error log entry is not valid JSON")
|
|
||||||
else:
|
|
||||||
print("✗ Error log file not created")
|
|
||||||
|
|
||||||
def test_performance_logging():
|
|
||||||
"""Test performance logging"""
|
|
||||||
print("\n=== Testing Performance Logging ===")
|
|
||||||
|
|
||||||
@log_performance('test_operation')
|
|
||||||
def slow_function():
|
|
||||||
time.sleep(0.1) # Simulate slow operation
|
|
||||||
return "result"
|
|
||||||
|
|
||||||
result = slow_function()
|
|
||||||
print(f"✓ Function returned: {result}")
|
|
||||||
|
|
||||||
# Check performance log
|
|
||||||
if os.path.exists('logs/performance.log'):
|
|
||||||
print("✓ Performance log file created")
|
|
||||||
|
|
||||||
# Read last line
|
|
||||||
with open('logs/performance.log', 'r') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
if lines:
|
|
||||||
try:
|
|
||||||
perf_entry = json.loads(lines[-1])
|
|
||||||
duration = perf_entry.get('extra_fields', {}).get('duration_ms', 0)
|
|
||||||
print(f"✓ Performance logged with duration: {duration}ms")
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("✗ Performance log entry is not valid JSON")
|
|
||||||
else:
|
|
||||||
print("✗ Performance log file not created")
|
|
||||||
|
|
||||||
def test_structured_logging():
|
|
||||||
"""Test structured logging format"""
|
|
||||||
print("\n=== Testing Structured Logging ===")
|
|
||||||
|
|
||||||
logger = get_logger('test.structured')
|
|
||||||
|
|
||||||
# Log with extra fields
|
|
||||||
logger.info("Structured log test", extra={
|
|
||||||
'extra_fields': {
|
|
||||||
'user_id': 123,
|
|
||||||
'action': 'test_action',
|
|
||||||
'metadata': {'key': 'value'}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Check main log
|
|
||||||
if os.path.exists('logs/talk2me.log'):
|
|
||||||
with open('logs/talk2me.log', 'r') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
if lines:
|
|
||||||
try:
|
|
||||||
# Find our test entry
|
|
||||||
for line in reversed(lines):
|
|
||||||
entry = json.loads(line)
|
|
||||||
if entry.get('message') == 'Structured log test':
|
|
||||||
print("✓ Structured log entry found")
|
|
||||||
print(f"✓ Contains timestamp: {'timestamp' in entry}")
|
|
||||||
print(f"✓ Contains hostname: {'hostname' in entry}")
|
|
||||||
print(f"✓ Contains extra fields: {'user_id' in entry}")
|
|
||||||
break
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("✗ Log entry is not valid JSON")
|
|
||||||
|
|
||||||
def test_log_rotation():
|
|
||||||
"""Test log rotation settings"""
|
|
||||||
print("\n=== Testing Log Rotation ===")
|
|
||||||
|
|
||||||
# Check if log files exist and their sizes
|
|
||||||
log_files = {
|
|
||||||
'talk2me.log': 'logs/talk2me.log',
|
|
||||||
'errors.log': 'logs/errors.log',
|
|
||||||
'access.log': 'logs/access.log',
|
|
||||||
'security.log': 'logs/security.log',
|
|
||||||
'performance.log': 'logs/performance.log'
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, path in log_files.items():
|
|
||||||
if os.path.exists(path):
|
|
||||||
size = os.path.getsize(path)
|
|
||||||
print(f"✓ {name}: {size} bytes")
|
|
||||||
else:
|
|
||||||
print(f"- {name}: not created yet")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run all tests"""
|
|
||||||
print("Error Logging System Tests")
|
|
||||||
print("==========================")
|
|
||||||
|
|
||||||
# Create a test Flask app
|
|
||||||
from flask import Flask
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config['LOG_LEVEL'] = 'DEBUG'
|
|
||||||
app.config['FLASK_ENV'] = 'testing'
|
|
||||||
|
|
||||||
# Initialize error logger
|
|
||||||
error_logger = ErrorLogger(app)
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
test_basic_logging()
|
|
||||||
test_error_logging()
|
|
||||||
test_performance_logging()
|
|
||||||
test_structured_logging()
|
|
||||||
test_log_rotation()
|
|
||||||
|
|
||||||
print("\n✅ All tests completed!")
|
|
||||||
print("\nCheck the logs directory for generated log files:")
|
|
||||||
print("- logs/talk2me.log - Main application log")
|
|
||||||
print("- logs/errors.log - Error log with stack traces")
|
|
||||||
print("- logs/performance.log - Performance metrics")
|
|
||||||
print("- logs/access.log - HTTP access log")
|
|
||||||
print("- logs/security.log - Security events")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,264 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Unit tests for session management system
|
|
||||||
"""
|
|
||||||
import unittest
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
from session_manager import SessionManager, UserSession, SessionResource
|
|
||||||
from flask import Flask, g, session
|
|
||||||
|
|
||||||
class TestSessionManager(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up test fixtures"""
|
|
||||||
self.temp_dir = tempfile.mkdtemp()
|
|
||||||
self.config = {
|
|
||||||
'max_session_duration': 3600,
|
|
||||||
'max_idle_time': 900,
|
|
||||||
'max_resources_per_session': 5, # Small limit for testing
|
|
||||||
'max_bytes_per_session': 1024 * 1024, # 1MB for testing
|
|
||||||
'cleanup_interval': 1, # 1 second for faster testing
|
|
||||||
'session_storage_path': self.temp_dir
|
|
||||||
}
|
|
||||||
self.manager = SessionManager(self.config)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Clean up test fixtures"""
|
|
||||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_create_session(self):
|
|
||||||
"""Test session creation"""
|
|
||||||
session = self.manager.create_session(
|
|
||||||
session_id='test-123',
|
|
||||||
user_id='user-1',
|
|
||||||
ip_address='127.0.0.1',
|
|
||||||
user_agent='Test Agent'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(session.session_id, 'test-123')
|
|
||||||
self.assertEqual(session.user_id, 'user-1')
|
|
||||||
self.assertEqual(session.ip_address, '127.0.0.1')
|
|
||||||
self.assertEqual(session.user_agent, 'Test Agent')
|
|
||||||
self.assertEqual(len(session.resources), 0)
|
|
||||||
|
|
||||||
def test_get_session(self):
|
|
||||||
"""Test session retrieval"""
|
|
||||||
self.manager.create_session(session_id='test-456')
|
|
||||||
session = self.manager.get_session('test-456')
|
|
||||||
|
|
||||||
self.assertIsNotNone(session)
|
|
||||||
self.assertEqual(session.session_id, 'test-456')
|
|
||||||
|
|
||||||
# Non-existent session
|
|
||||||
session = self.manager.get_session('non-existent')
|
|
||||||
self.assertIsNone(session)
|
|
||||||
|
|
||||||
def test_add_resource(self):
|
|
||||||
"""Test adding resources to session"""
|
|
||||||
self.manager.create_session(session_id='test-789')
|
|
||||||
|
|
||||||
# Add a resource
|
|
||||||
resource = self.manager.add_resource(
|
|
||||||
session_id='test-789',
|
|
||||||
resource_type='audio_file',
|
|
||||||
resource_id='audio-1',
|
|
||||||
path='/tmp/test.wav',
|
|
||||||
size_bytes=1024,
|
|
||||||
metadata={'format': 'wav'}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIsNotNone(resource)
|
|
||||||
self.assertEqual(resource.resource_id, 'audio-1')
|
|
||||||
self.assertEqual(resource.resource_type, 'audio_file')
|
|
||||||
self.assertEqual(resource.size_bytes, 1024)
|
|
||||||
|
|
||||||
# Check session updated
|
|
||||||
session = self.manager.get_session('test-789')
|
|
||||||
self.assertEqual(len(session.resources), 1)
|
|
||||||
self.assertEqual(session.total_bytes_used, 1024)
|
|
||||||
|
|
||||||
def test_resource_limits(self):
|
|
||||||
"""Test resource limit enforcement"""
|
|
||||||
self.manager.create_session(session_id='test-limits')
|
|
||||||
|
|
||||||
# Add resources up to limit
|
|
||||||
for i in range(5):
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-limits',
|
|
||||||
resource_type='temp_file',
|
|
||||||
resource_id=f'file-{i}',
|
|
||||||
size_bytes=100
|
|
||||||
)
|
|
||||||
|
|
||||||
session = self.manager.get_session('test-limits')
|
|
||||||
self.assertEqual(len(session.resources), 5)
|
|
||||||
|
|
||||||
# Add one more - should remove oldest
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-limits',
|
|
||||||
resource_type='temp_file',
|
|
||||||
resource_id='file-new',
|
|
||||||
size_bytes=100
|
|
||||||
)
|
|
||||||
|
|
||||||
session = self.manager.get_session('test-limits')
|
|
||||||
self.assertEqual(len(session.resources), 5) # Still 5
|
|
||||||
self.assertNotIn('file-0', session.resources) # Oldest removed
|
|
||||||
self.assertIn('file-new', session.resources) # New one added
|
|
||||||
|
|
||||||
def test_size_limits(self):
|
|
||||||
"""Test size limit enforcement"""
|
|
||||||
self.manager.create_session(session_id='test-size')
|
|
||||||
|
|
||||||
# Add a large resource
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-size',
|
|
||||||
resource_type='audio_file',
|
|
||||||
resource_id='large-1',
|
|
||||||
size_bytes=500 * 1024 # 500KB
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add another large resource
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-size',
|
|
||||||
resource_type='audio_file',
|
|
||||||
resource_id='large-2',
|
|
||||||
size_bytes=600 * 1024 # 600KB - would exceed 1MB limit
|
|
||||||
)
|
|
||||||
|
|
||||||
session = self.manager.get_session('test-size')
|
|
||||||
# First resource should be removed to make space
|
|
||||||
self.assertNotIn('large-1', session.resources)
|
|
||||||
self.assertIn('large-2', session.resources)
|
|
||||||
self.assertLessEqual(session.total_bytes_used, 1024 * 1024)
|
|
||||||
|
|
||||||
def test_remove_resource(self):
|
|
||||||
"""Test resource removal"""
|
|
||||||
self.manager.create_session(session_id='test-remove')
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-remove',
|
|
||||||
resource_type='temp_file',
|
|
||||||
resource_id='to-remove',
|
|
||||||
size_bytes=1000
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove resource
|
|
||||||
success = self.manager.remove_resource('test-remove', 'to-remove')
|
|
||||||
self.assertTrue(success)
|
|
||||||
|
|
||||||
# Check it's gone
|
|
||||||
session = self.manager.get_session('test-remove')
|
|
||||||
self.assertEqual(len(session.resources), 0)
|
|
||||||
self.assertEqual(session.total_bytes_used, 0)
|
|
||||||
|
|
||||||
def test_cleanup_session(self):
|
|
||||||
"""Test session cleanup"""
|
|
||||||
# Create session with resources
|
|
||||||
self.manager.create_session(session_id='test-cleanup')
|
|
||||||
|
|
||||||
# Create actual temp file
|
|
||||||
temp_file = os.path.join(self.temp_dir, 'test-file.txt')
|
|
||||||
with open(temp_file, 'w') as f:
|
|
||||||
f.write('test content')
|
|
||||||
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-cleanup',
|
|
||||||
resource_type='temp_file',
|
|
||||||
path=temp_file,
|
|
||||||
size_bytes=12
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cleanup session
|
|
||||||
success = self.manager.cleanup_session('test-cleanup')
|
|
||||||
self.assertTrue(success)
|
|
||||||
|
|
||||||
# Check session is gone
|
|
||||||
session = self.manager.get_session('test-cleanup')
|
|
||||||
self.assertIsNone(session)
|
|
||||||
|
|
||||||
# Check file is deleted
|
|
||||||
self.assertFalse(os.path.exists(temp_file))
|
|
||||||
|
|
||||||
def test_session_info(self):
|
|
||||||
"""Test session info retrieval"""
|
|
||||||
self.manager.create_session(
|
|
||||||
session_id='test-info',
|
|
||||||
ip_address='192.168.1.1'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id='test-info',
|
|
||||||
resource_type='audio_file',
|
|
||||||
size_bytes=2048
|
|
||||||
)
|
|
||||||
|
|
||||||
info = self.manager.get_session_info('test-info')
|
|
||||||
self.assertIsNotNone(info)
|
|
||||||
self.assertEqual(info['session_id'], 'test-info')
|
|
||||||
self.assertEqual(info['ip_address'], '192.168.1.1')
|
|
||||||
self.assertEqual(info['resource_count'], 1)
|
|
||||||
self.assertEqual(info['total_bytes_used'], 2048)
|
|
||||||
|
|
||||||
def test_stats(self):
|
|
||||||
"""Test statistics calculation"""
|
|
||||||
# Create multiple sessions
|
|
||||||
for i in range(3):
|
|
||||||
self.manager.create_session(session_id=f'test-stats-{i}')
|
|
||||||
self.manager.add_resource(
|
|
||||||
session_id=f'test-stats-{i}',
|
|
||||||
resource_type='temp_file',
|
|
||||||
size_bytes=1000
|
|
||||||
)
|
|
||||||
|
|
||||||
stats = self.manager.get_stats()
|
|
||||||
self.assertEqual(stats['active_sessions'], 3)
|
|
||||||
self.assertEqual(stats['active_resources'], 3)
|
|
||||||
self.assertEqual(stats['active_bytes'], 3000)
|
|
||||||
self.assertEqual(stats['total_sessions_created'], 3)
|
|
||||||
|
|
||||||
def test_metrics_export(self):
|
|
||||||
"""Test metrics export"""
|
|
||||||
self.manager.create_session(session_id='test-metrics')
|
|
||||||
metrics = self.manager.export_metrics()
|
|
||||||
|
|
||||||
self.assertIn('sessions', metrics)
|
|
||||||
self.assertIn('resources', metrics)
|
|
||||||
self.assertIn('limits', metrics)
|
|
||||||
self.assertEqual(metrics['sessions']['active'], 1)
|
|
||||||
|
|
||||||
class TestFlaskIntegration(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up Flask app for testing"""
|
|
||||||
self.app = Flask(__name__)
|
|
||||||
self.app.config['TESTING'] = True
|
|
||||||
self.app.config['SECRET_KEY'] = 'test-secret'
|
|
||||||
self.temp_dir = tempfile.mkdtemp()
|
|
||||||
self.app.config['UPLOAD_FOLDER'] = self.temp_dir
|
|
||||||
|
|
||||||
# Initialize session manager
|
|
||||||
from session_manager import init_app
|
|
||||||
init_app(self.app)
|
|
||||||
|
|
||||||
self.client = self.app.test_client()
|
|
||||||
self.ctx = self.app.test_request_context()
|
|
||||||
self.ctx.push()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Clean up"""
|
|
||||||
self.ctx.pop()
|
|
||||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_before_request_handler(self):
|
|
||||||
"""Test Flask before_request integration"""
|
|
||||||
with self.client:
|
|
||||||
# Make a request
|
|
||||||
response = self.client.get('/')
|
|
||||||
|
|
||||||
# Session should be created
|
|
||||||
with self.client.session_transaction() as sess:
|
|
||||||
self.assertIn('session_id', sess)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,146 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test script for request size limits
|
|
||||||
"""
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
|
|
||||||
BASE_URL = "http://localhost:5005"
|
|
||||||
|
|
||||||
def test_json_size_limit():
|
|
||||||
"""Test JSON payload size limit"""
|
|
||||||
print("\n=== Testing JSON Size Limit ===")
|
|
||||||
|
|
||||||
# Create a large JSON payload (over 1MB)
|
|
||||||
large_data = {
|
|
||||||
"text": "x" * (2 * 1024 * 1024), # 2MB of text
|
|
||||||
"source_lang": "English",
|
|
||||||
"target_lang": "Spanish"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/translate", json=large_data)
|
|
||||||
print(f"Status: {response.status_code}")
|
|
||||||
if response.status_code == 413:
|
|
||||||
print(f"✓ Correctly rejected large JSON: {response.json()}")
|
|
||||||
else:
|
|
||||||
print(f"✗ Should have rejected large JSON")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
def test_audio_size_limit():
|
|
||||||
"""Test audio file size limit"""
|
|
||||||
print("\n=== Testing Audio Size Limit ===")
|
|
||||||
|
|
||||||
# Create a fake large audio file (over 25MB)
|
|
||||||
large_audio = io.BytesIO(b"x" * (30 * 1024 * 1024)) # 30MB
|
|
||||||
|
|
||||||
files = {
|
|
||||||
'audio': ('large_audio.wav', large_audio, 'audio/wav')
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
'source_lang': 'English'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/transcribe", files=files, data=data)
|
|
||||||
print(f"Status: {response.status_code}")
|
|
||||||
if response.status_code == 413:
|
|
||||||
print(f"✓ Correctly rejected large audio: {response.json()}")
|
|
||||||
else:
|
|
||||||
print(f"✗ Should have rejected large audio")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
def test_valid_requests():
|
|
||||||
"""Test that valid-sized requests are accepted"""
|
|
||||||
print("\n=== Testing Valid Size Requests ===")
|
|
||||||
|
|
||||||
# Small JSON payload
|
|
||||||
small_data = {
|
|
||||||
"text": "Hello world",
|
|
||||||
"source_lang": "English",
|
|
||||||
"target_lang": "Spanish"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/translate", json=small_data)
|
|
||||||
print(f"Small JSON - Status: {response.status_code}")
|
|
||||||
if response.status_code != 413:
|
|
||||||
print("✓ Small JSON accepted")
|
|
||||||
else:
|
|
||||||
print("✗ Small JSON should be accepted")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
# Small audio file
|
|
||||||
small_audio = io.BytesIO(b"RIFF" + b"x" * 1000) # 1KB fake WAV
|
|
||||||
files = {
|
|
||||||
'audio': ('small_audio.wav', small_audio, 'audio/wav')
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
'source_lang': 'English'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/transcribe", files=files, data=data)
|
|
||||||
print(f"Small audio - Status: {response.status_code}")
|
|
||||||
if response.status_code != 413:
|
|
||||||
print("✓ Small audio accepted")
|
|
||||||
else:
|
|
||||||
print("✗ Small audio should be accepted")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
def test_admin_endpoints():
|
|
||||||
"""Test admin endpoints for size limits"""
|
|
||||||
print("\n=== Testing Admin Endpoints ===")
|
|
||||||
|
|
||||||
headers = {'X-Admin-Token': os.environ.get('ADMIN_TOKEN', 'default-admin-token')}
|
|
||||||
|
|
||||||
# Get current limits
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{BASE_URL}/admin/size-limits", headers=headers)
|
|
||||||
print(f"Get limits - Status: {response.status_code}")
|
|
||||||
if response.status_code == 200:
|
|
||||||
limits = response.json()
|
|
||||||
print(f"✓ Current limits: {limits['limits_human']}")
|
|
||||||
else:
|
|
||||||
print(f"✗ Failed to get limits: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
# Update limits
|
|
||||||
new_limits = {
|
|
||||||
"max_audio_size": "30MB",
|
|
||||||
"max_json_size": 2097152 # 2MB in bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/admin/size-limits",
|
|
||||||
json=new_limits, headers=headers)
|
|
||||||
print(f"\nUpdate limits - Status: {response.status_code}")
|
|
||||||
if response.status_code == 200:
|
|
||||||
result = response.json()
|
|
||||||
print(f"✓ Updated limits: {result['new_limits_human']}")
|
|
||||||
else:
|
|
||||||
print(f"✗ Failed to update limits: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("Request Size Limit Tests")
|
|
||||||
print("========================")
|
|
||||||
print(f"Testing against: {BASE_URL}")
|
|
||||||
print("\nMake sure the Flask app is running on port 5005")
|
|
||||||
|
|
||||||
input("\nPress Enter to start tests...")
|
|
||||||
|
|
||||||
test_valid_requests()
|
|
||||||
test_json_size_limit()
|
|
||||||
test_audio_size_limit()
|
|
||||||
test_admin_endpoints()
|
|
||||||
|
|
||||||
print("\n✅ All tests completed!")
|
|
@ -1,78 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""
|
|
||||||
TTS Debug Script - Tests connection to the OpenAI TTS server
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
def test_tts_connection(server_url, api_key, text="Hello, this is a test message"):
|
|
||||||
"""Test connection to the TTS server"""
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": f"Bearer {api_key}"
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"input": text,
|
|
||||||
"voice": "echo",
|
|
||||||
"response_format": "mp3",
|
|
||||||
"speed": 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Sending request to: {server_url}")
|
|
||||||
print(f"Headers: {headers}")
|
|
||||||
print(f"Payload: {json.dumps(payload, indent=2)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
server_url,
|
|
||||||
headers=headers,
|
|
||||||
json=payload,
|
|
||||||
timeout=15
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Response status code: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print("Success! Received audio data")
|
|
||||||
# Save to file
|
|
||||||
output_file = "tts_test_output.mp3"
|
|
||||||
with open(output_file, "wb") as f:
|
|
||||||
f.write(response.content)
|
|
||||||
print(f"Saved audio to {output_file}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print("Error in response")
|
|
||||||
try:
|
|
||||||
error_data = response.json()
|
|
||||||
print(f"Error details: {json.dumps(error_data, indent=2)}")
|
|
||||||
except:
|
|
||||||
print(f"Raw response: {response.text[:500]}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error during request: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = ArgumentParser(description="Test connection to OpenAI TTS server")
|
|
||||||
parser.add_argument("--url", default="http://localhost:5050/v1/audio/speech", help="TTS server URL")
|
|
||||||
parser.add_argument("--key", default=os.environ.get("TTS_API_KEY", ""), help="API key")
|
|
||||||
parser.add_argument("--text", default="Hello, this is a test message", help="Text to synthesize")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.key:
|
|
||||||
print("Error: API key is required. Use --key argument or set TTS_API_KEY environment variable.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
success = test_tts_connection(args.url, args.key, args.text)
|
|
||||||
return 0 if success else 1
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user