'commit'
This commit is contained in:
@@ -6,6 +6,7 @@ import json
|
||||
import ssl
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
from urllib.parse import urlencode
|
||||
@@ -163,10 +164,11 @@ class XunFeiAudioEvaluator:
|
||||
# 查找read_chapter节点
|
||||
read_chapter = root.find('.//read_chapter')
|
||||
if read_chapter is not None:
|
||||
# 保持字段名一致,使用completeness_score
|
||||
self.evaluation_results = {
|
||||
'accuracy_score': float(read_chapter.get('accuracy_score', 0)),
|
||||
'fluency_score': float(read_chapter.get('fluency_score', 0)),
|
||||
'completeness_score': float(read_chapter.get('integrity_score', 0)), # 修复字段名
|
||||
'completeness_score': float(read_chapter.get('integrity_score', 0)),
|
||||
'standard_score': float(read_chapter.get('standard_score', 0)),
|
||||
'total_score': float(read_chapter.get('total_score', 0)),
|
||||
'word_count': int(read_chapter.get('word_count', 0)),
|
||||
@@ -206,7 +208,8 @@ class XunFeiAudioEvaluator:
|
||||
summary += f"总得分: {self.evaluation_results.get('total_score', 0):.4f}\n"
|
||||
summary += f"准确度得分: {self.evaluation_results.get('accuracy_score', 0):.4f}\n"
|
||||
summary += f"流畅度得分: {self.evaluation_results.get('fluency_score', 0):.4f}\n"
|
||||
summary += f"完整度得分: {self.evaluation_results.get('integrity_score', 0):.4f}\n"
|
||||
# 修复这里,使用completeness_score
|
||||
summary += f"完整度得分: {self.evaluation_results.get('completeness_score', 0):.4f}\n"
|
||||
summary += f"标准度得分: {self.evaluation_results.get('standard_score', 0):.4f}\n"
|
||||
summary += f"单词数量: {self.evaluation_results.get('word_count', 0)}\n"
|
||||
summary += f"是否被拒绝: {'是' if self.evaluation_results.get('is_rejected', False) else '否'}\n"
|
||||
@@ -269,8 +272,8 @@ if __name__ == '__main__':
|
||||
appid = XF_APPID
|
||||
api_secret = XF_APISECRET
|
||||
api_key = XF_APIKEY
|
||||
audio_file = "./1.mp3"
|
||||
|
||||
#audio_file = "./1.mp3"
|
||||
audio_file=r'D:\dsWork\dsProject\dsLightRag\static\audio\audio_1f0a8c47db2d4f9ba7674055a352cab8.wav'
|
||||
# 创建评测器实例
|
||||
evaluator = XunFeiAudioEvaluator(appid, api_key, api_secret, audio_file, "english")
|
||||
|
||||
|
Binary file not shown.
@@ -4,6 +4,7 @@ import uuid
|
||||
import tempfile
|
||||
import shutil
|
||||
import sys
|
||||
import logging
|
||||
from fastapi import APIRouter, UploadFile, File
|
||||
|
||||
# 配置日志
|
||||
@@ -30,7 +31,13 @@ async def save_audio(audio: UploadFile = File(...)):
|
||||
content = await audio.read()
|
||||
with open(temp_file, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
# 3. 保存到正式目录
|
||||
file_name = f"audio_{uuid.uuid4().hex}.wav"
|
||||
file_path = os.path.join(UPLOAD_DIR, file_name)
|
||||
shutil.copy2(temp_file, file_path)
|
||||
logger.info(f"已保存文件到: {file_path}")
|
||||
|
||||
# 2. 讯飞评分
|
||||
from KeDaXunFei.XunFeiAudioEvaluator import XunFeiAudioEvaluator
|
||||
evaluator = XunFeiAudioEvaluator(
|
||||
@@ -41,11 +48,8 @@ async def save_audio(audio: UploadFile = File(...)):
|
||||
language="english"
|
||||
)
|
||||
results, eval_time = evaluator.run_evaluation()
|
||||
|
||||
# 3. 保存到正式目录
|
||||
file_name = f"audio_{uuid.uuid4().hex}.wav"
|
||||
file_path = os.path.join(UPLOAD_DIR, file_name)
|
||||
shutil.copy2(temp_file, file_path)
|
||||
print(evaluator.get_evaluation_summary())
|
||||
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
|
Binary file not shown.
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>在线录音机</title>
|
||||
<title>英语朗读评测</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -183,6 +183,78 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.evaluation-container {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: rgba(30, 41, 59, 0.5);
|
||||
border-radius: 10px;
|
||||
display: none;
|
||||
border: 1px solid rgba(94, 234, 212, 0.1);
|
||||
}
|
||||
|
||||
.evaluation-container.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.score-card {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(94, 234, 212, 0.2);
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #5eead4;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 12px;
|
||||
color: #cbd5e1;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.words-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.word-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(94, 234, 212, 0.1);
|
||||
}
|
||||
|
||||
.word-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.word-content {
|
||||
font-weight: 500;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.word-score {
|
||||
color: #5eead4;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
audio {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
@@ -416,6 +488,33 @@
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">上传进度: 0%</div>
|
||||
</div>
|
||||
|
||||
<!-- 评分结果显示区域 -->
|
||||
<div class="evaluation-container" id="evaluationContainer">
|
||||
<h3 style="color: #5eead4; margin-bottom: 15px;">📊 评测结果</h3>
|
||||
<div class="score-card">
|
||||
<div class="score-item">
|
||||
<span class="score-value" id="totalScore">--</span>
|
||||
<span class="score-label">总分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-value" id="accuracyScore">--</span>
|
||||
<span class="score-label">准确度</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-value" id="fluencyScore">--</span>
|
||||
<span class="score-label">流利度</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-value" id="completenessScore">--</span>
|
||||
<span class="score-label">完整度</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="words-list" id="wordsList">
|
||||
<h4 style="color: #5eead4; margin-bottom: 10px;">单词评分</h4>
|
||||
<div id="wordsContent">等待评测结果...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -456,6 +555,13 @@
|
||||
this.uploadProgress = document.getElementById('uploadProgress');
|
||||
this.progressFill = document.getElementById('progressFill');
|
||||
this.progressText = document.getElementById('progressText');
|
||||
this.evaluationContainer = document.getElementById('evaluationContainer');
|
||||
this.totalScore = document.getElementById('totalScore');
|
||||
this.accuracyScore = document.getElementById('accuracyScore');
|
||||
this.fluencyScore = document.getElementById('fluencyScore');
|
||||
this.completenessScore = document.getElementById('completenessScore');
|
||||
this.wordsList = document.getElementById('wordsList');
|
||||
this.wordsContent = document.getElementById('wordsContent');
|
||||
}
|
||||
|
||||
setupCanvas() {
|
||||
@@ -493,7 +599,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 设置音频上下文用于可视化
|
||||
this.setupAudioContext(stream);
|
||||
|
||||
const mimeType = this.getMimeType();
|
||||
@@ -523,19 +628,12 @@
|
||||
this.updateButtons();
|
||||
};
|
||||
|
||||
this.mediaRecorder.start(1000); // 每秒触发一次dataavailable事件
|
||||
this.mediaRecorder.start(1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('录音错误:', error);
|
||||
this.updateStatus('无法访问麦克风,请检查权限设置', 'error');
|
||||
|
||||
if (error.name === 'NotAllowedError') {
|
||||
alert('请允许访问麦克风权限才能录音');
|
||||
} else if (error.name === 'NotFoundError') {
|
||||
alert('未找到麦克风设备');
|
||||
} else {
|
||||
alert('录音初始化失败: ' + error.message);
|
||||
}
|
||||
alert('录音初始化失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +658,6 @@
|
||||
if (!this.isRecording) return;
|
||||
|
||||
this.animationId = requestAnimationFrame(draw);
|
||||
|
||||
this.analyser.getByteFrequencyData(this.dataArray);
|
||||
|
||||
this.canvasCtx.fillStyle = 'rgba(15, 23, 42, 0.7)';
|
||||
@@ -573,7 +670,6 @@
|
||||
for (let i = 0; i < this.dataArray.length; i++) {
|
||||
barHeight = (this.dataArray[i] / 255) * this.canvas.height * 0.8;
|
||||
|
||||
// 创建渐变
|
||||
const gradient = this.canvasCtx.createLinearGradient(0, this.canvas.height - barHeight, 0, this.canvas.height);
|
||||
gradient.addColorStop(0, '#5eead4');
|
||||
gradient.addColorStop(1, '#0ea5e9');
|
||||
@@ -594,7 +690,6 @@
|
||||
this.animationId = null;
|
||||
}
|
||||
|
||||
// 清空画布
|
||||
this.canvasCtx.fillStyle = 'rgba(15, 23, 42, 0.7)';
|
||||
this.canvasCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
@@ -609,7 +704,6 @@
|
||||
|
||||
const mimeType = supportedTypes[format];
|
||||
|
||||
// 检查浏览器是否支持选定的格式
|
||||
if (!MediaRecorder.isTypeSupported(mimeType)) {
|
||||
console.warn(`${format} 格式不被支持,回退到 WebM`);
|
||||
return 'audio/webm;codecs=opus';
|
||||
@@ -636,7 +730,6 @@
|
||||
const url = URL.createObjectURL(this.recordedBlob);
|
||||
this.audioPlayer.src = url;
|
||||
|
||||
// 显示录音信息
|
||||
const duration = Math.round((Date.now() - this.startTime) / 1000);
|
||||
const sizeInKB = Math.round(this.recordedBlob.size / 1024);
|
||||
|
||||
@@ -645,7 +738,7 @@
|
||||
this.audioContainer.classList.add('show');
|
||||
|
||||
this.updateStatus(`录音完成!时长: ${duration}秒, 大小: ${sizeInKB}KB`, 'success');
|
||||
this.updateButtons(); // 添加这一行来更新按钮状态
|
||||
this.updateButtons();
|
||||
}
|
||||
|
||||
playRecording() {
|
||||
@@ -662,31 +755,19 @@
|
||||
this.uploadProgress.classList.add('show');
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
// 根据选择的格式确定文件扩展名
|
||||
const format = this.audioFormat.value;
|
||||
const extensions = {
|
||||
'webm': 'webm',
|
||||
'mp4': 'mp4',
|
||||
'wav': 'wav'
|
||||
};
|
||||
|
||||
const extensions = {'webm': 'webm', 'mp4': 'mp4', 'wav': 'wav'};
|
||||
const fileName = `recording_${Date.now()}.${extensions[format]}`;
|
||||
|
||||
formData.append('audio', this.recordedBlob, fileName);
|
||||
formData.append('format', format);
|
||||
formData.append('duration', this.duration.textContent);
|
||||
formData.append('fileSize', this.fileSize.textContent);
|
||||
|
||||
try {
|
||||
// 模拟上传进度
|
||||
this.simulateUploadProgress();
|
||||
|
||||
const response = await fetch('/api/xunFei/save-audio', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
headers: {'Accept': 'application/json'}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -694,32 +775,52 @@
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
this.uploadProgress.classList.remove('show');
|
||||
this.updateStatus('上传成功!', 'success');
|
||||
|
||||
console.log('上传结果:', result);
|
||||
|
||||
// 这里可以添加成功后的处理逻辑
|
||||
if (result.audioUrl) {
|
||||
alert(`录音上传成功!\n文件URL: ${result.audioUrl}\n文件名: ${fileName}`);
|
||||
|
||||
if (result.success) {
|
||||
this.updateStatus('评测完成!', 'success');
|
||||
this.displayEvaluationResults(result.evaluation);
|
||||
console.log('评测结果:', result.evaluation);
|
||||
} else {
|
||||
alert('录音上传成功!');
|
||||
throw new Error(result.error || '未知错误');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传错误:', error);
|
||||
this.uploadProgress.classList.remove('show');
|
||||
this.updateStatus('上传失败: ' + error.message, 'error');
|
||||
alert('上传失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 更详细的错误处理
|
||||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||
alert('网络连接失败,请检查网络设置');
|
||||
} else if (error.message.includes('Failed to fetch')) {
|
||||
alert('无法连接到服务器,请检查API地址是否正确');
|
||||
} else {
|
||||
alert('上传失败: ' + error.message);
|
||||
}
|
||||
displayEvaluationResults(evaluation) {
|
||||
if (!evaluation) return;
|
||||
|
||||
// 显示评分卡片
|
||||
this.totalScore.textContent = evaluation.total_score ? evaluation.total_score.toFixed(1) : '--';
|
||||
this.accuracyScore.textContent = evaluation.accuracy_score ? evaluation.accuracy_score.toFixed(1) : '--';
|
||||
this.fluencyScore.textContent = evaluation.fluency_score ? evaluation.fluency_score.toFixed(1) : '--';
|
||||
this.completenessScore.textContent = evaluation.completeness_score ? evaluation.completeness_score.toFixed(1) : '--';
|
||||
|
||||
this.evaluationContainer.classList.add('show');
|
||||
|
||||
// 显示单词评分
|
||||
if (evaluation.words && evaluation.words.length > 0) {
|
||||
let wordsHTML = '';
|
||||
evaluation.words.forEach((word, index) => {
|
||||
const score = word.total_score ? word.total_score.toFixed(1) : '0.0';
|
||||
const color = score >= 80 ? '#10b981' : score >= 60 ? '#f59e0b' : '#ef4444';
|
||||
wordsHTML += `
|
||||
<div class="word-item">
|
||||
<span class="word-content">${word.content || `单词${index + 1}`}</span>
|
||||
<span class="word-score" style="color: ${color}">${score}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
this.wordsContent.innerHTML = wordsHTML;
|
||||
} else {
|
||||
this.wordsContent.innerHTML = '<p style="color: #cbd5e1; text-align: center;">无单词评分数据</p>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,7 +829,7 @@
|
||||
const interval = setInterval(() => {
|
||||
progress += Math.random() * 15;
|
||||
if (progress >= 90) {
|
||||
progress = 90; // 等待实际上传完成
|
||||
progress = 90;
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
@@ -772,7 +873,6 @@
|
||||
this.status.className = 'status ' + type;
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
cleanup() {
|
||||
this.stopTimer();
|
||||
this.stopVisualization();
|
||||
@@ -791,38 +891,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化录音器
|
||||
const recorder = new AudioRecorder();
|
||||
|
||||
// 页面卸载时清理资源
|
||||
window.addEventListener('beforeunload', () => {
|
||||
recorder.cleanup();
|
||||
});
|
||||
|
||||
// 处理页面可见性变化
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden && recorder.isRecording) {
|
||||
console.log('页面隐藏,录音继续进行...');
|
||||
}
|
||||
});
|
||||
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
if (!recorder.isRecording && !recorder.recordBtn.disabled) {
|
||||
recorder.startRecording();
|
||||
} else if (recorder.isRecording) {
|
||||
recorder.stopRecording();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加提示信息
|
||||
console.log('🎙️ 在线录音机已加载完成!');
|
||||
console.log('💡 快捷键:空格键 - 开始/停止录音');
|
||||
console.log('📋 支持的格式:WebM (推荐), MP4, WAV');
|
||||
console.log('🔧 请确保已允许麦克风权限');
|
||||
window.addEventListener('beforeunload', () => recorder.cleanup());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user