File size: 5,144 Bytes
23590ba
edcef16
 
8b6f23d
 
d114f5b
 
4164780
d114f5b
4164780
d114f5b
c80867b
 
 
07478ab
d114f5b
 
 
 
 
250b797
 
d114f5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250b797
d114f5b
 
 
 
 
 
 
 
 
 
 
 
 
4164780
d114f5b
 
 
 
 
 
 
 
b90ae45
d114f5b
edcef16
d114f5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b90ae45
d114f5b
 
b90ae45
d114f5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8b6f23d
d114f5b
 
 
 
 
4164780
d114f5b
 
4164780
d114f5b
 
 
 
edcef16
 
 
981b713
9225a67
8b6f23d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import json
import tempfile
import ffmpeg
import os
import wave
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)
Swagger(app)

@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ộ
    """
    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)

    # Kiểm tra kích thước tệp
    if os.path.getsize(webm_path) < 100:
        os.remove(webm_path)
        return jsonify({"error": "Tệp âm thanh quá nhỏ hoặc rỗng!"}), 400

    # Đường dẫn file WAV tạm thời sau khi chuyển đổi
    wav_path = None
    wf = None
    try:
        # Kiểm tra tệp WebM bằng ffprobe trước khi chuyển đổi
        try:
            probe = ffmpeg.probe(webm_path)
            if 'streams' not in probe or not any(s['codec_type'] == 'audio' for s in probe['streams']):
                raise ValueError("Tệp không chứa luồng âm thanh hợp lệ!")
        except ffmpeg.Error as e:
            error_message = e.stderr.decode('utf-8') if e.stderr else str(e)
            return jsonify({"error": f"Lỗi kiểm tra tệp WebM: {error_message}"}), 500

        # Chuyển đổi WebM sang WAV mono PCM
        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
            format="wav"
        )
        # Thêm cờ -vn để bỏ qua video nếu có và -y để ghi đè
        ffmpeg.run(stream, overwrite_output=True, quiet=True, capture_stdout=True, capture_stderr=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:
                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:
        error_message = e.stderr.decode('utf-8') if e.stderr else str(e)
        return jsonify({"error": f"Lỗi chuyển đổi âm thanh từ WebM sang WAV: {error_message}"}), 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)