File size: 7,848 Bytes
d6cebe9
 
6d3487b
 
 
 
d6cebe9
6d3487b
 
d6cebe9
 
6dee894
d6cebe9
ec8fa7d
d6cebe9
6dee894
ec8fa7d
6d3487b
476003d
d6cebe9
 
 
 
 
6d3487b
476003d
c2e5478
 
476003d
d6cebe9
6d3487b
476003d
6d3487b
 
a875366
6d3487b
d71f113
6d3487b
 
 
476003d
217f9d2
2a52065
 
 
476003d
6dee894
476003d
6dee894
 
 
 
476003d
d6cebe9
 
476003d
 
 
d6cebe9
 
 
 
 
 
 
 
 
 
476003d
c90cf25
 
 
 
476003d
6d3487b
 
476003d
 
 
6d3487b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476003d
6d3487b
 
476003d
6d3487b
 
 
d71f113
6d3487b
 
476003d
6d3487b
d6cebe9
 
 
 
476003d
 
 
 
 
 
 
6d3487b
 
 
 
 
 
476003d
6d3487b
d6cebe9
 
 
 
 
 
476003d
6d3487b
 
d6cebe9
476003d
6d3487b
 
 
 
 
 
d6cebe9
476003d
6dee894
ec8fa7d
6d3487b
d6cebe9
476003d
6dee894
59c18e6
6d3487b
59c18e6
 
6dee894
59c18e6
6dee894
 
59c18e6
6d3487b
6dee894
 
 
 
 
 
d6cebe9
476003d
d6cebe9
 
 
476003d
d6cebe9
 
 
476003d
d6cebe9
 
 
476003d
d6cebe9
 
 
 
 
476003d
d6cebe9
6dee894
d71f113
d6cebe9
 
6d3487b
d6cebe9
ec8fa7d
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
import whisper
import logging
from contextlib import contextmanager
import tempfile
import os
import keras
import librosa
import numpy as np
import re
import Levenshtein
import tensorflow as tf

from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from utils_api import get_features


#вывод в консоль для просмотри на hugging face
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 
    handlers=[logging.StreamHandler()] 
)

# Установка временной директории для кэша Numba
os.environ['NUMBA_CACHE_DIR'] = '/tmp'

# Инициализация FastAPI приложения
app = FastAPI(port=8000)

# Настройка CORS (Cross-Origin Resource Sharing) для обработки запросов с разных доменов
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["GET, POST"],
    allow_headers=["*"],
)

# Инициализация и загрузка модели Whisper для распознавания речи
cache_dir = "/tmp/whisper_cache"
os.makedirs(cache_dir, exist_ok=True)
whisper_model = whisper.load_model("tiny", download_root=cache_dir)

# загрузка параметров модели
filepath = "best_model.keras"
if not os.path.exists(filepath):
    raise FileNotFoundError(f"Model file not found at {filepath}")\
        
model = tf.keras.models.load_model(filepath, compile=False)
logging.info(model.summary())
# Контекстный менеджер для временных аудио файлов
@contextmanager
def temporary_audio_file(audio_bytes):
    """
    Создает временный файл для хранения аудио данных и автоматически удаляет его после использования
    """
    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
        tmp_file.write(audio_bytes)
        tmp_file.flush()
        tmp_filename = tmp_file.name
    try:
        yield tmp_filename
    finally:
        if os.path.exists(tmp_filename):
            os.remove(tmp_filename)

# Корневой endpoint
@app.get("/")
async def read_root():
    return {"message": "Welcome to the Defects_model API"}

# Endpoint для сохранения аудио файлов
@app.post("/save-audio")
async def save_audio(file: UploadFile = File(...)):
    """
    Обработчик для сохранения загруженных аудио файлов
    """
    if not file.content_type.startswith("audio/"):
        raise HTTPException(status_code=400, detail="Invalid file type")

    file_path = os.path.join("audio", file.filename)
    os.makedirs("audio", exist_ok=True)
    try:
        with open(file_path, "wb") as f:
            content = await file.read()
            f.write(content)
        return JSONResponse(
            content={"message": "File saved successfully", "filePath": file_path},
            status_code=200,
        )
    except Exception as e:
        return JSONResponse(content={"error": str(e)}, status_code=500)

# Настройка пути для файла логов
log_file_path = os.path.join("/tmp", "server.log")

# Настройка логирования для отслеживания работы сервера
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[logging.StreamHandler()]
)

# Основной endpoint для обработки аудио
@app.post("/process-audio")
async def process_audio(
    audio: UploadFile = File(...), 
    phrase: str = Form(...)
):
    """
    Главный обработчик для анализа аудио файлов:
    - Делает предсказание моделью
    - Прогоняет аудио через openai-whisper для проверки фразы
    - Сравнивает полученный текст с ожидаемой фразой
    """
    # Проверка формата файла
    if audio.content_type != "audio/mpeg":
        raise HTTPException(
            status_code=400, detail="Invalid file type. Only MP3 files are supported."
        )

    try:
        # Чтение аудио файла
        audio_bytes = await audio.read()

        if not audio_bytes:
            raise HTTPException(status_code=400, detail="Received empty file")

        logging.info(f"Received audio bytes: {len(audio_bytes)} bytes")

        # Обработка аудио во временном файле
        with temporary_audio_file(audio_bytes) as tmp_filename:
            logging.info(f"Temporary file created: {tmp_filename}")

            # Загрузка аудио данных
            audio_data, sample_rate = librosa.load(tmp_filename, sr=None)
            logging.info(
                f"Audio loaded: sample rate = {sample_rate}, data shape = {audio_data.shape}"
            )
            if not audio_data.any() or sample_rate == 0:
                raise ValueError("Empty or invalid audio data.")
            
            # Извлечение признаков из аудио
            features = get_features(tmp_filename)
            # features = np.expand_dims(features, axis=0)  # Add batch dimension
            logging.info(f"Features extracted: shape = {features.shape}")

            # Получение предсказания от модели
            class_weights = {0: 0.5460790960451978, 1: 1.0068333333333332, 2: 1000.696369636963697}

            prediction = model.predict(features)
            logging.info(f"Prediction shape: {prediction.shape}")

            #умножаем предикт на веса классов
            for j in range(prediction.shape[1]):
                prediction[0, j] *= class_weights.get(j, 1.0)
                prediction[0, j] *= 10

            logging.info(f"Prediction: {prediction}")
            response_answer = np.argmax(prediction)
            if (response_answer == 0): 
                response_answer = 1
            else:
                response_answer = 0
            logging.info(f"Right or with defects: 1 or 0: {response_answer}")

            # Транскрибация аудио с помощью Whisper
            transcription_result = whisper_model.transcribe(tmp_filename, language="russian")
            transcribed_text = transcription_result["text"].lower().strip()

            # Очистка транскрибированного текста
            transcribed_text_clean = re.sub(r'[^\w\s]', '', transcribed_text) 
            logging.info(f"Transcribed text (cleaned): {transcribed_text_clean}")

            # Сравнение с ожидаемой фразой
            lev_distance = Levenshtein.distance(transcribed_text_clean, phrase.lower().strip())
            phrase_length = max(len(transcribed_text_clean), len(phrase))

            # Определение допустимого расстояния Левенштейна
            max_acceptable_distance = 0.5 * phrase_length
            match_phrase = lev_distance <= max_acceptable_distance

            logging.info(f"Expected phrase: {phrase}, Is correct: {match_phrase}, Transcribed text: {transcribed_text_clean}, Levenshtein distance: {lev_distance}")

            # Возврат результатов
            return {
                "prediction": response_answer,
                "match_phrase": match_phrase
            }

    except Exception as e:
        logging.exception(f"Error processing audio: {e}")
        raise HTTPException(status_code=500, detail="Internal server error")