Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +58 -54
src/streamlit_app.py
CHANGED
|
@@ -2,51 +2,52 @@ import streamlit as st
|
|
| 2 |
import torch
|
| 3 |
import numpy as np
|
| 4 |
from pydub import AudioSegment
|
| 5 |
-
from transformers import pipeline, WhisperProcessor
|
| 6 |
-
|
| 7 |
-
# Eğer kullanmayacaksanız requirements.txt'den ve importtan kaldırabilirsiniz.
|
| 8 |
-
import webrtcvad
|
| 9 |
from phonemizer.backend import EspeakBackend
|
| 10 |
from fastdtw import fastdtw
|
| 11 |
from scipy.signal import find_peaks
|
| 12 |
import librosa
|
| 13 |
import io
|
| 14 |
-
import os
|
| 15 |
-
# import ffmpeg
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
|
| 22 |
-
|
| 23 |
-
model_path = os.path.join(BASE_DIR, "../model_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab")
|
| 24 |
-
tokenizer_path = os.path.join(BASE_DIR, "../tokenizer_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab")
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
# model_path = 'model_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab'
|
| 29 |
-
# tokenizer_path = 'tokenizer_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab'
|
| 30 |
-
|
| 31 |
-
# Model ve tokenizer klasörlerinin varlığını kontrol et
|
| 32 |
if not os.path.exists(model_path) or not os.path.exists(tokenizer_path):
|
| 33 |
-
st.error(f"Hata: Model veya tokenizer klasörleri bulunamadı. Lütfen '{model_path}' ve '{tokenizer_path}' yollarını ve dosya yapısını kontrol edin.")
|
| 34 |
st.stop() # Uygulamayı durdur
|
| 35 |
|
| 36 |
@st.cache_resource
|
| 37 |
def load_asr_components():
|
| 38 |
"""ASR modelini ve işlemcisini önbelleğe alır."""
|
| 39 |
try:
|
|
|
|
| 40 |
processor = WhisperProcessor.from_pretrained(tokenizer_path, language='aze', task='transcribe')
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
"
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
"task": "transcribe",
|
| 51 |
"language": "azerbaijani",
|
| 52 |
"num_beams": 2,
|
|
@@ -56,33 +57,35 @@ def load_asr_components():
|
|
| 56 |
"logprob_threshold": -1.0,
|
| 57 |
"return_timestamps": True,
|
| 58 |
}
|
| 59 |
-
|
| 60 |
-
asr_pipeline = pipeline(**pipeline_config)
|
| 61 |
return processor, asr_pipeline
|
| 62 |
except Exception as e:
|
| 63 |
-
st.error(f"ASR bileşenleri yüklenirken hata oluştu: {e}. Lütfen model ve tokenizer yollarını kontrol edin.")
|
| 64 |
return None, None
|
| 65 |
|
| 66 |
processor, asr_pipeline = load_asr_components()
|
| 67 |
|
| 68 |
class DictionAnalyzer:
|
| 69 |
def __init__(self):
|
| 70 |
-
# phonemizer backend'i de önbelleğe alınabilir veya burada başlatılabilir
|
| 71 |
-
# EspeakBackend'in başlatılması biraz zaman alabilir, ilk yüklemede sorun olmaması için burada.
|
| 72 |
self.phonemizer = EspeakBackend("az")
|
| 73 |
|
| 74 |
def analyze_prosody(self, y, sr):
|
| 75 |
try:
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
except Exception:
|
| 78 |
-
pitch = np.array([
|
| 79 |
-
|
| 80 |
energy = librosa.feature.rms(y=y)[0]
|
|
|
|
| 81 |
peaks, _ = find_peaks(energy, height=np.mean(energy) if energy.size > 0 else 0)
|
| 82 |
|
| 83 |
return {
|
| 84 |
-
"average_pitch": np.
|
| 85 |
-
"pitch_variance": np.
|
| 86 |
"energy_peaks": len(peaks)
|
| 87 |
}
|
| 88 |
|
|
@@ -110,8 +113,9 @@ class DictionAnalyzer:
|
|
| 110 |
return {"phonetic_distance": float('inf')}
|
| 111 |
|
| 112 |
try:
|
|
|
|
| 113 |
dtw_distance, _ = fastdtw(numeric_transcript, numeric_reference, dist=lambda x, y: np.linalg.norm(x - y))
|
| 114 |
-
except ValueError: #
|
| 115 |
dtw_distance = float('inf')
|
| 116 |
return {"phonetic_distance": dtw_distance}
|
| 117 |
|
|
@@ -120,7 +124,7 @@ class DictionAnalyzer:
|
|
| 120 |
Dəyəri yaxşı, orta, pis kateqoriyasına uyğun olaraq 1-10 arası bal verir.
|
| 121 |
is_lower_better: Əgər dəyər nə qədər aşağı olarsa, o qədər yaxşıdırsa True qeyd edin.
|
| 122 |
"""
|
| 123 |
-
if value is None or np.isnan(value) or value == float('inf'): #
|
| 124 |
return "Hesaplanamadı", 0
|
| 125 |
|
| 126 |
if is_lower_better:
|
|
@@ -139,14 +143,14 @@ class DictionAnalyzer:
|
|
| 139 |
return "Pis", 2
|
| 140 |
|
| 141 |
def full_analysis(self, audio_data, reference_text):
|
| 142 |
-
# audio_data dictionary
|
| 143 |
y = audio_data["raw"]
|
| 144 |
sr = audio_data["sampling_rate"]
|
| 145 |
|
| 146 |
audio_duration = librosa.get_duration(y=y, sr=sr)
|
| 147 |
|
| 148 |
if asr_pipeline is None:
|
| 149 |
-
st.error("ASR pipeline
|
| 150 |
return None
|
| 151 |
|
| 152 |
try:
|
|
@@ -160,11 +164,11 @@ class DictionAnalyzer:
|
|
| 160 |
fluency = self.analyze_speed_and_fluency(transcript, audio_duration)
|
| 161 |
pronunciation = self.compare_pronunciation(transcript, reference_text)
|
| 162 |
|
| 163 |
-
#
|
| 164 |
-
# Bunlar
|
| 165 |
prosody_avg_pitch_eval = self.evaluate_score(prosody["average_pitch"], (100, 250), (70, 300), is_lower_better=False)
|
| 166 |
prosody_pitch_variance_eval = self.evaluate_score(prosody["pitch_variance"], (500, 3000), (200, 5000), is_lower_better=False)
|
| 167 |
-
# energy_peaks
|
| 168 |
prosody_energy_peaks_eval = self.evaluate_score(prosody["energy_peaks"] / audio_duration if audio_duration > 0 else 0, (0.1, 0.5), (0.05, 0.7), is_lower_better=False)
|
| 169 |
|
| 170 |
fluency_wps_eval = self.evaluate_score(fluency["words_per_second"], (2.0, 3.0), (1.5, 3.5), is_lower_better=False)
|
|
@@ -194,16 +198,16 @@ class DictionAnalyzer:
|
|
| 194 |
|
| 195 |
# Streamlit UI
|
| 196 |
st.title("Diksiyon Analiz Uygulaması")
|
| 197 |
-
st.write("Ses dosyanızı
|
| 198 |
|
| 199 |
-
uploaded_file = st.file_uploader("Ses Dosyası
|
| 200 |
reference_text_input = st.text_area("Referans Metin Girin",
|
| 201 |
"Salam, hər vaxtınız xeyr, mənim adım Əlidir. Salam, Əli bəy. Sizə necə kömək edə bilərəm? Kartımın şifrəsini dəyişmək istəyirəm və şifrəmi unutmuşam. Şifrənizi dəyişdirmək üçün sizdən bəzi məlumatlar istəyəcəm. Zəhmət olmasa, adınızı, soyadınızı və bir də vəsigənin fin kodunu deyə bilərsiniz. Əlbəttə, deyə bilərəm. Mənim adım Ələl Əkbəlli. Fin kodum isə 7CRH7UE. Zəhmət olmasa, yenə təkrar bilərsiniz fin kodu? 7-C-R-H-7-U-E Təşəkkür edirəm, Əli bəy. Şifrənizi sıfırlamaq üçün sizə indi kod gəlməlidir telefona, gəldi, hal-hazırda? Bəli, bəli, kod gəldi. İndi parolu dəyişə bilərəm. Bəli, dəyişə bilərsiniz? Oldu, hazırda şifrəmi dəyişdirdim. Aha, hal-hazırda əməliyyatınız uğurla tamamlanıb və şifrəniz dəyişilib. Yeni şifrədən istifadə edə bilərsiniz, kart hesabınıza daxil olmaq üçün. Başqa sualınız varmı? Xeyr, çox təşəkkür edəm sizə. Buyurun, gününüz xoş geçsin. Sağ olun.")
|
| 202 |
|
| 203 |
if uploaded_file is not None and reference_text_input:
|
| 204 |
-
st.audio(uploaded_file, format='audio/wav') # Kullanıcının
|
| 205 |
|
| 206 |
-
with st.spinner('Ses dosyası
|
| 207 |
try:
|
| 208 |
audio_bytes = uploaded_file.read()
|
| 209 |
audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes))
|
|
@@ -219,20 +223,20 @@ if uploaded_file is not None and reference_text_input:
|
|
| 219 |
|
| 220 |
# Eğer sample rate 16000 değilse, yeniden örnekle
|
| 221 |
if audio_segment.frame_rate != 16000:
|
| 222 |
-
st.info(f"Ses dosyası {audio_segment.frame_rate} Hz. 16000 Hz'e
|
| 223 |
audio_array = librosa.resample(y=audio_array, orig_sr=audio_segment.frame_rate, target_sr=16000)
|
| 224 |
|
| 225 |
audio_input_for_analysis = {"raw": audio_array, "sampling_rate": 16000}
|
| 226 |
|
| 227 |
except Exception as e:
|
| 228 |
-
st.error(f"Ses dosyası
|
| 229 |
audio_input_for_analysis = None
|
| 230 |
|
| 231 |
if audio_input_for_analysis:
|
| 232 |
analyzer = DictionAnalyzer()
|
| 233 |
|
| 234 |
-
st.write("Analiz
|
| 235 |
-
with st.spinner('Analiz
|
| 236 |
report = analyzer.full_analysis(audio_input_for_analysis, reference_text_input)
|
| 237 |
|
| 238 |
if report:
|
|
@@ -253,4 +257,4 @@ if uploaded_file is not None and reference_text_input:
|
|
| 253 |
else:
|
| 254 |
st.error("Analiz tamamlanamadı.")
|
| 255 |
elif uploaded_file is None and st.button("Analiz Et"):
|
| 256 |
-
st.warning("Lütfen bir ses dosyası
|
|
|
|
| 2 |
import torch
|
| 3 |
import numpy as np
|
| 4 |
from pydub import AudioSegment
|
| 5 |
+
from transformers import pipeline, WhisperProcessor, AutoModelForSpeechSeq2Seq # AutoModelForSpeechSeq2Seq əlavə edildi
|
| 6 |
+
import webrtcvad
|
|
|
|
|
|
|
| 7 |
from phonemizer.backend import EspeakBackend
|
| 8 |
from fastdtw import fastdtw
|
| 9 |
from scipy.signal import find_peaks
|
| 10 |
import librosa
|
| 11 |
import io
|
| 12 |
+
import os
|
|
|
|
| 13 |
|
| 14 |
+
# Hugging Face Spaces-də `src` qovluğu strukturunu nəzərə alaraq
|
| 15 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # src qovluğunun yolu
|
| 16 |
+
PROJECT_ROOT = os.path.join(BASE_DIR, "..") # layihənin kök qovluğu
|
| 17 |
|
| 18 |
+
model_path = os.path.join(PROJECT_ROOT, "model_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab")
|
| 19 |
+
tokenizer_path = os.path.join(PROJECT_ROOT, "tokenizer_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab")
|
| 20 |
|
| 21 |
+
# Model və tokenizer klasörlərinin varlığını kontrol et
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
if not os.path.exists(model_path) or not os.path.exists(tokenizer_path):
|
| 23 |
+
st.error(f"Hata: Model veya tokenizer klasörleri bulunamadı. Lütfen '{model_path}' ve '{tokenizer_path}' yollarını ve Hugging Face Space-deki dosya yapısını kontrol edin. Model fayllarının ({model_path}/pytorch_model.bin or model.safetensors) mövcud olduğundan əmin olun.")
|
| 24 |
st.stop() # Uygulamayı durdur
|
| 25 |
|
| 26 |
@st.cache_resource
|
| 27 |
def load_asr_components():
|
| 28 |
"""ASR modelini ve işlemcisini önbelleğe alır."""
|
| 29 |
try:
|
| 30 |
+
# Önce işlemciyi yükle
|
| 31 |
processor = WhisperProcessor.from_pretrained(tokenizer_path, language='aze', task='transcribe')
|
| 32 |
|
| 33 |
+
# Modeli doğrudan Whisper model sınıfı olarak yükle
|
| 34 |
+
# Bu, AutoModelForCTC hatasının önüne geçmek için kritikdir.
|
| 35 |
+
# Eğer modeliniz safetensors formatındaysa, from_pretrained otomatik olarak onu tanıyacaktır.
|
| 36 |
+
model = AutoModelForSpeechSeq2Seq.from_pretrained(
|
| 37 |
+
model_path,
|
| 38 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
| 39 |
+
device_map="auto" if torch.cuda.is_available() else None # GPU varsa otomatik olaraq cihazı seç
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# ASR pipeline-ı qur
|
| 43 |
+
asr_pipeline = pipeline(
|
| 44 |
+
"automatic-speech-recognition",
|
| 45 |
+
model=model, # Yüklenen model obyektini ötürürük
|
| 46 |
+
tokenizer=processor.tokenizer,
|
| 47 |
+
feature_extractor=processor.feature_extractor,
|
| 48 |
+
device=0 if torch.cuda.is_available() else -1,
|
| 49 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
| 50 |
+
generate_kwargs={
|
| 51 |
"task": "transcribe",
|
| 52 |
"language": "azerbaijani",
|
| 53 |
"num_beams": 2,
|
|
|
|
| 57 |
"logprob_threshold": -1.0,
|
| 58 |
"return_timestamps": True,
|
| 59 |
}
|
| 60 |
+
)
|
|
|
|
| 61 |
return processor, asr_pipeline
|
| 62 |
except Exception as e:
|
| 63 |
+
st.error(f"ASR bileşenleri yüklenirken hata oluştu: {e}. Lütfen model ve tokenizer yollarını kontrol edin. Modelinizde `pytorch_model.bin` veya `model.safetensors` faylının olduğundan əmin olun.")
|
| 64 |
return None, None
|
| 65 |
|
| 66 |
processor, asr_pipeline = load_asr_components()
|
| 67 |
|
| 68 |
class DictionAnalyzer:
|
| 69 |
def __init__(self):
|
|
|
|
|
|
|
| 70 |
self.phonemizer = EspeakBackend("az")
|
| 71 |
|
| 72 |
def analyze_prosody(self, y, sr):
|
| 73 |
try:
|
| 74 |
+
# pyin fasilələr səbəbindən NaN dəyərlər qaytara bilər, bunları idarə edin
|
| 75 |
+
pitch, _, _ = librosa.pyin(y, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'), sr=sr, frame_length=2048, hop_length=512)
|
| 76 |
+
# NaN dəyərləri sıfırla dolduraraq və ya qulaq ardına vuraraq orta dəyəri hesablayın
|
| 77 |
+
pitch = pitch[~np.isnan(pitch)] # NaN dəyərləri çıxar
|
| 78 |
+
if pitch.size == 0: pitch = np.array([0.0]) # Boş qalarsa sıfır ver
|
| 79 |
except Exception:
|
| 80 |
+
pitch = np.array([0.0]) # Hata durumunda da sıfır ver
|
| 81 |
+
|
| 82 |
energy = librosa.feature.rms(y=y)[0]
|
| 83 |
+
# Enerji pikleri boş enerji dizileri için handle
|
| 84 |
peaks, _ = find_peaks(energy, height=np.mean(energy) if energy.size > 0 else 0)
|
| 85 |
|
| 86 |
return {
|
| 87 |
+
"average_pitch": np.mean(pitch),
|
| 88 |
+
"pitch_variance": np.var(pitch),
|
| 89 |
"energy_peaks": len(peaks)
|
| 90 |
}
|
| 91 |
|
|
|
|
| 113 |
return {"phonetic_distance": float('inf')}
|
| 114 |
|
| 115 |
try:
|
| 116 |
+
# DTW üçün numpy.ndarray obyektlərini istifadə edin
|
| 117 |
dtw_distance, _ = fastdtw(numeric_transcript, numeric_reference, dist=lambda x, y: np.linalg.norm(x - y))
|
| 118 |
+
except ValueError: # Boş ardıcıllıqlar üçün DTW xətasını idarə et
|
| 119 |
dtw_distance = float('inf')
|
| 120 |
return {"phonetic_distance": dtw_distance}
|
| 121 |
|
|
|
|
| 124 |
Dəyəri yaxşı, orta, pis kateqoriyasına uyğun olaraq 1-10 arası bal verir.
|
| 125 |
is_lower_better: Əgər dəyər nə qədər aşağı olarsa, o qədər yaxşıdırsa True qeyd edin.
|
| 126 |
"""
|
| 127 |
+
if value is None or np.isnan(value) or value == float('inf'): # Geçərsiz dəyərləri idarə et
|
| 128 |
return "Hesaplanamadı", 0
|
| 129 |
|
| 130 |
if is_lower_better:
|
|
|
|
| 143 |
return "Pis", 2
|
| 144 |
|
| 145 |
def full_analysis(self, audio_data, reference_text):
|
| 146 |
+
# audio_data dictionary olaraq gəlməlidir: {"raw": np.array, "sampling_rate": int}
|
| 147 |
y = audio_data["raw"]
|
| 148 |
sr = audio_data["sampling_rate"]
|
| 149 |
|
| 150 |
audio_duration = librosa.get_duration(y=y, sr=sr)
|
| 151 |
|
| 152 |
if asr_pipeline is None:
|
| 153 |
+
st.error("ASR pipeline yüklənmədi. Analiz yapılamıyor.")
|
| 154 |
return None
|
| 155 |
|
| 156 |
try:
|
|
|
|
| 164 |
fluency = self.analyze_speed_and_fluency(transcript, audio_duration)
|
| 165 |
pronunciation = self.compare_pronunciation(transcript, reference_text)
|
| 166 |
|
| 167 |
+
# Dəyərləndirmə aralıqları: Bu aralıqlar, modelinizin performansı və istədiyiniz metriklərə görə ayarlanmalıdır.
|
| 168 |
+
# Bunlar sadəcə nümunələrdir.
|
| 169 |
prosody_avg_pitch_eval = self.evaluate_score(prosody["average_pitch"], (100, 250), (70, 300), is_lower_better=False)
|
| 170 |
prosody_pitch_variance_eval = self.evaluate_score(prosody["pitch_variance"], (500, 3000), (200, 5000), is_lower_better=False)
|
| 171 |
+
# energy_peaks üçün audio_duration'a görə bir oran düşünə bilərsiniz. Örneğin: 0.1-0.5 peak/saniyə yaxşı ola bilər.
|
| 172 |
prosody_energy_peaks_eval = self.evaluate_score(prosody["energy_peaks"] / audio_duration if audio_duration > 0 else 0, (0.1, 0.5), (0.05, 0.7), is_lower_better=False)
|
| 173 |
|
| 174 |
fluency_wps_eval = self.evaluate_score(fluency["words_per_second"], (2.0, 3.0), (1.5, 3.5), is_lower_better=False)
|
|
|
|
| 198 |
|
| 199 |
# Streamlit UI
|
| 200 |
st.title("Diksiyon Analiz Uygulaması")
|
| 201 |
+
st.write("Ses dosyanızı yükləyin və referans metin girərək diksiyonunuzu analiz edin.")
|
| 202 |
|
| 203 |
+
uploaded_file = st.file_uploader("Ses Dosyası Yükləyin (.wav, .mp3)", type=["wav", "mp3"])
|
| 204 |
reference_text_input = st.text_area("Referans Metin Girin",
|
| 205 |
"Salam, hər vaxtınız xeyr, mənim adım Əlidir. Salam, Əli bəy. Sizə necə kömək edə bilərəm? Kartımın şifrəsini dəyişmək istəyirəm və şifrəmi unutmuşam. Şifrənizi dəyişdirmək üçün sizdən bəzi məlumatlar istəyəcəm. Zəhmət olmasa, adınızı, soyadınızı və bir də vəsigənin fin kodunu deyə bilərsiniz. Əlbəttə, deyə bilərəm. Mənim adım Ələl Əkbəlli. Fin kodum isə 7CRH7UE. Zəhmət olmasa, yenə təkrar bilərsiniz fin kodu? 7-C-R-H-7-U-E Təşəkkür edirəm, Əli bəy. Şifrənizi sıfırlamaq üçün sizə indi kod gəlməlidir telefona, gəldi, hal-hazırda? Bəli, bəli, kod gəldi. İndi parolu dəyişə bilərəm. Bəli, dəyişə bilərsiniz? Oldu, hazırda şifrəmi dəyişdirdim. Aha, hal-hazırda əməliyyatınız uğurla tamamlanıb və şifrəniz dəyişilib. Yeni şifrədən istifadə edə bilərsiniz, kart hesabınıza daxil olmaq üçün. Başqa sualınız varmı? Xeyr, çox təşəkkür edəm sizə. Buyurun, gününüz xoş geçsin. Sağ olun.")
|
| 206 |
|
| 207 |
if uploaded_file is not None and reference_text_input:
|
| 208 |
+
st.audio(uploaded_file, format='audio/wav') # Kullanıcının yüklədiyi səsi oynat
|
| 209 |
|
| 210 |
+
with st.spinner('Ses dosyası işlənir...'):
|
| 211 |
try:
|
| 212 |
audio_bytes = uploaded_file.read()
|
| 213 |
audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes))
|
|
|
|
| 223 |
|
| 224 |
# Eğer sample rate 16000 değilse, yeniden örnekle
|
| 225 |
if audio_segment.frame_rate != 16000:
|
| 226 |
+
st.info(f"Ses dosyası {audio_segment.frame_rate} Hz. 16000 Hz'e dönüştürülür...")
|
| 227 |
audio_array = librosa.resample(y=audio_array, orig_sr=audio_segment.frame_rate, target_sr=16000)
|
| 228 |
|
| 229 |
audio_input_for_analysis = {"raw": audio_array, "sampling_rate": 16000}
|
| 230 |
|
| 231 |
except Exception as e:
|
| 232 |
+
st.error(f"Ses dosyası oxunarkən və ya işlənərkən hata oluştu: {e}")
|
| 233 |
audio_input_for_analysis = None
|
| 234 |
|
| 235 |
if audio_input_for_analysis:
|
| 236 |
analyzer = DictionAnalyzer()
|
| 237 |
|
| 238 |
+
st.write("Analiz edilir, lütfən gözləyin...")
|
| 239 |
+
with st.spinner('Analiz tamamlanır...'):
|
| 240 |
report = analyzer.full_analysis(audio_input_for_analysis, reference_text_input)
|
| 241 |
|
| 242 |
if report:
|
|
|
|
| 257 |
else:
|
| 258 |
st.error("Analiz tamamlanamadı.")
|
| 259 |
elif uploaded_file is None and st.button("Analiz Et"):
|
| 260 |
+
st.warning("Lütfen bir ses dosyası yükləyin və referans metin girin.")
|