import os import wave import json import uuid import tempfile import ffmpeg from flask import Flask, request, jsonify from flask_cors import CORS from vosk import Model, KaldiRecognizer from flasgger import Swagger # Thư mục chứa model MODEL_PATH = "model/vosk-model" print("\u2705 Đang tải model Vosk...") model = Model(MODEL_PATH) # Khởi tạo Flask app app = Flask(__name__) CORS(app) # Cho phép CORS để React app truy cập Swagger(app) # Khởi tạo Swagger cho tài liệu API @app.route("/") def home(): """API Home --- responses: 200: description: API đang chạy """ return "\u2705 Vosk STT API đang chạy!" @app.route("/stt", methods=["POST"]) def stt(): """Chuyển đổi giọng nói thành văn bản (Speech-to-Text) --- consumes: - multipart/form-data parameters: - in: formData name: file type: file required: true description: File âm thanh WebM (sẽ được chuyển đổi sang WAV mono PCM) responses: 200: description: Kết quả chuyển đổi văn bản schema: type: object properties: text: type: string example: "Xin chào thế giới" 400: description: Lỗi nếu file âm thanh không hợp lệ hoặc không tìm thấy 500: description: Lỗi server nội bộ """ # Kiểm tra trường 'file' để khớp với React if "file" not in request.files: return jsonify({"error": "Không tìm thấy file âm thanh! Vui lòng gửi trường 'file'."}), 400 audio_file = request.files["file"] if audio_file.filename == "": return jsonify({"error": "Không có file được chọn!"}), 400 # Lưu file WebM tạm thời with tempfile.NamedTemporaryFile(suffix=".webm", delete=False) as temp_webm_file: webm_path = temp_webm_file.name audio_file.save(webm_path) # Đường dẫn file WAV tạm thời sau khi chuyển đổi wav_path = None wf = None try: # Chuyển đổi WebM sang WAV mono PCM bằng ffmpeg wav_path = tempfile.mktemp(suffix=".wav") stream = ffmpeg.input(webm_path) stream = ffmpeg.output( stream, wav_path, acodec="pcm_s16le", # PCM 16-bit signed little-endian ac=1, # Mono ar=16000 # Tần số mẫu 16kHz, phù hợp với Vosk ) ffmpeg.run(stream, overwrite_output=True, quiet=True) # Mở file WAV đã chuyển đổi wf = wave.open(wav_path, "rb") # Kiểm tra định dạng WAV mono PCM if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE": return jsonify({"error": "Định dạng WAV sau chuyển đổi không đúng!"}), 400 # Khởi tạo KaldiRecognizer rec = KaldiRecognizer(model, wf.getframerate()) result_text = "" # Đọc và xử lý dữ liệu âm thanh while True: data = wf.readframes(4000) if len(data) == 0: break if rec.AcceptWaveform(data): result = json.loads(rec.Result()) result_text += result.get("text", "") + " " else: # Thêm kết quả từng phần để cải thiện độ chính xác partial_result = json.loads(rec.PartialResult()) if partial_result.get("partial", ""): result_text += partial_result["partial"] + " " # Trả về kết quả đã xử lý final_text = result_text.strip() if not final_text: final_text = "Không nhận diện được nội dung âm thanh." return jsonify({"text": final_text}) except ffmpeg.Error as e: return jsonify({"error": f"Lỗi chuyển đổi âm thanh từ WebM sang WAV: {str(e.stderr.decode())}"}), 500 except Exception as e: return jsonify({"error": f"Lỗi xử lý âm thanh: {str(e)}"}), 500 finally: # Đóng file WAV nếu đã mở if wf is not None: wf.close() # Xóa các file tạm for path in [webm_path, wav_path]: if path and os.path.exists(path): os.remove(path) if __name__ == "__main__": app.run(host="0.0.0.0", port=7860, debug=True)