183 lines
5.3 KiB
Python
183 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
录音后端服务 V2 - 集成语音转文字
|
|
"""
|
|
import warnings
|
|
warnings.filterwarnings('ignore')
|
|
|
|
from flask import Flask, request, jsonify, send_from_directory
|
|
from flask_cors import CORS
|
|
from werkzeug.utils import secure_filename
|
|
import os
|
|
import ssl
|
|
from datetime import datetime
|
|
|
|
# 导入转写模块 - 使用 Vosk
|
|
from transcribe_api import transcribe_audio
|
|
|
|
MODEL_PATH = '/home/ubuntu/nanocode/audio-recorder/models/cn-model'
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
# 配置
|
|
UPLOAD_FOLDER = '/home/ubuntu/nanocode/audio-recorder/uploads'
|
|
ALLOWED_EXTENSIONS = {'webm', 'mp4', 'wav', 'mp3', 'ogg'}
|
|
MAX_CONTENT_LENGTH = 100 * 1024 * 1024
|
|
|
|
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
|
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
|
|
|
|
def allowed_file(filename):
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
|
|
@app.route('/api/upload', methods=['POST'])
|
|
def upload_audio():
|
|
"""接收音频文件上传"""
|
|
if 'audio' not in request.files:
|
|
return jsonify({'error': '没有文件'}), 400
|
|
|
|
file = request.files['audio']
|
|
if file.filename == '':
|
|
return jsonify({'error': '文件名为空'}), 400
|
|
|
|
if file and allowed_file(file.filename):
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f"recording_{timestamp}.webm"
|
|
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
|
|
|
file.save(filepath)
|
|
file_size = os.path.getsize(filepath)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': filename,
|
|
'filepath': filepath,
|
|
'size': file_size,
|
|
'size_human': f"{file_size / 1024:.1f} KB",
|
|
'timestamp': timestamp
|
|
}), 200
|
|
|
|
return jsonify({'error': '不支持的文件类型'}), 400
|
|
|
|
|
|
@app.route('/api/transcribe', methods=['POST'])
|
|
def transcribe():
|
|
"""语音转文字"""
|
|
if 'audio' not in request.files:
|
|
return jsonify({'error': '没有音频文件'}), 400
|
|
|
|
file = request.files['audio']
|
|
if file.filename == '':
|
|
return jsonify({'error': '文件名为空'}), 400
|
|
|
|
try:
|
|
# 保存上传的音频
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f"recording_{timestamp}.webm"
|
|
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
|
file.save(filepath)
|
|
|
|
print(f"🎵 收到音频: {filename}, 大小: {os.path.getsize(filepath)} bytes")
|
|
|
|
# 转写
|
|
text, error = transcribe_audio(filepath)
|
|
|
|
if error:
|
|
print(f"❌ 转写失败: {error}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': error,
|
|
'filename': filename
|
|
}), 500
|
|
|
|
print(f"✅ 转写成功: {text[:50]}...")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'text': text or '(未识别到语音)',
|
|
'filename': filename,
|
|
'model_type': 'vosk' if os.path.exists(MODEL_PATH) else 'mock'
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f"❌ 服务器错误: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': str(e)
|
|
}), 500
|
|
|
|
|
|
@app.route('/api/recordings', methods=['GET'])
|
|
def list_recordings():
|
|
"""列出所有录音文件"""
|
|
recordings = []
|
|
|
|
for filename in sorted(os.listdir(UPLOAD_FOLDER), reverse=True):
|
|
if allowed_file(filename):
|
|
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
|
stat = os.stat(filepath)
|
|
recordings.append({
|
|
'filename': filename,
|
|
'size': stat.st_size,
|
|
'size_human': f"{stat.st_size / 1024:.1f} KB",
|
|
'created': datetime.fromtimestamp(stat.st_mtime).isoformat()
|
|
})
|
|
|
|
return jsonify({
|
|
'count': len(recordings),
|
|
'recordings': recordings
|
|
})
|
|
|
|
|
|
@app.route('/api/recordings/<filename>', methods=['GET'])
|
|
def get_recording(filename):
|
|
"""获取单个录音文件"""
|
|
return send_from_directory(UPLOAD_FOLDER, filename)
|
|
|
|
|
|
@app.route('/api/health', methods=['GET'])
|
|
def health_check():
|
|
"""健康检查"""
|
|
model_exists = os.path.exists(MODEL_PATH)
|
|
return jsonify({
|
|
'status': 'ok',
|
|
'model_loaded': model_exists,
|
|
'model_path': MODEL_PATH,
|
|
'upload_folder': UPLOAD_FOLDER,
|
|
'recordings_count': len([f for f in os.listdir(UPLOAD_FOLDER) if allowed_file(f)])
|
|
})
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return send_from_directory('../frontend', 'index.html')
|
|
|
|
|
|
@app.route('/<path:path>')
|
|
def static_files(path):
|
|
return send_from_directory('../frontend', path)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
context.load_cert_chain(
|
|
'/home/ubuntu/nanocode/audio-recorder/cert.pem',
|
|
'/home/ubuntu/nanocode/audio-recorder/key.pem'
|
|
)
|
|
|
|
print(f"🎙️ HTTPS 录音服务 V2 启动中...")
|
|
print(f"📁 上传目录: {UPLOAD_FOLDER}")
|
|
print(f"🔤 模型路径: {MODEL_PATH}")
|
|
print(f"🔒 HTTPS 地址: https://129.211.173.87:8891")
|
|
print(f"⚠️ 提示: {'✅ 模型已安装' if os.path.exists(MODEL_PATH) else '⚠️ 使用模拟转写,请安装模型'}")
|
|
|
|
app.run(
|
|
host='0.0.0.0',
|
|
port=8891,
|
|
ssl_context=context,
|
|
debug=False
|
|
)
|