#!/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/', 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('/') 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 )