Ilkinism commited on
Commit
dca1e83
·
verified ·
1 Parent(s): baaac73

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +244 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,246 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import torch
3
+ import numpy as np
4
+ from pydub import AudioSegment
5
+ from transformers import pipeline, WhisperProcessor
6
+ # webrtcvad: Bu kütüphane genel VAD için kullanılır ancak mevcut analiz akışınızda doğrudan bir işlevi yok.
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 # Dosya yollarını yönetmek için
15
+
16
+ # Model ve tokenizer yolları (yerel klasörler için göreceli yollar)
17
+ # Hugging Face Spaces'te bu klasörler projenizin kök dizininde olacaktır.
18
+ model_path = './model_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab/'
19
+ tokenizer_path = './tokenizer_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab/'
20
+
21
+ # Model ve tokenizer klasörlerinin 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 dosya yapısını kontrol edin.")
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
+ processor = WhisperProcessor.from_pretrained(tokenizer_path, language='aze', task='transcribe')
31
+
32
+ pipeline_config = {
33
+ "task": "automatic-speech-recognition",
34
+ "model": model_path,
35
+ "tokenizer": processor.tokenizer,
36
+ "feature_extractor": processor.feature_extractor,
37
+ "device": 0 if torch.cuda.is_available() else -1, # CUDA varsa GPU kullan
38
+ "torch_dtype": torch.float16 if torch.cuda.is_available() else torch.float32,
39
+ "generate_kwargs": {
40
+ "task": "transcribe",
41
+ "language": "azerbaijani",
42
+ "num_beams": 2,
43
+ "condition_on_prev_tokens": False,
44
+ "compression_ratio_threshold": 2.4,
45
+ "temperature": (0.0, 0.2, 0.4),
46
+ "logprob_threshold": -1.0,
47
+ "return_timestamps": True,
48
+ }
49
+ }
50
+ asr_pipeline = pipeline(**pipeline_config)
51
+ return processor, asr_pipeline
52
+ except Exception as e:
53
+ st.error(f"ASR bileşenleri yüklenirken hata oluştu: {e}. Lütfen model ve tokenizer yollarını kontrol edin.")
54
+ return None, None
55
+
56
+ processor, asr_pipeline = load_asr_components()
57
+
58
+ class DictionAnalyzer:
59
+ def __init__(self):
60
+ # phonemizer backend'i de önbelleğe alınabilir veya burada başlatılabilir
61
+ # EspeakBackend'in başlatılması biraz zaman alabilir, ilk yüklemede sorun olmaması için burada.
62
+ self.phonemizer = EspeakBackend("az")
63
+
64
+ def analyze_prosody(self, y, sr):
65
+ try:
66
+ pitch, _, _ = librosa.pyin(y, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'))
67
+ except Exception:
68
+ pitch = np.array([np.nan]) # Handle cases where pyin might fail
69
+
70
+ energy = librosa.feature.rms(y=y)[0]
71
+ peaks, _ = find_peaks(energy, height=np.mean(energy) if energy.size > 0 else 0)
72
+
73
+ return {
74
+ "average_pitch": np.nanmean(pitch) if not np.all(np.isnan(pitch)) else 0,
75
+ "pitch_variance": np.nanvar(pitch) if not np.all(np.isnan(pitch)) else 0,
76
+ "energy_peaks": len(peaks)
77
+ }
78
+
79
+ def analyze_speed_and_fluency(self, transcript, audio_duration_seconds):
80
+ words = len(transcript.split())
81
+ if audio_duration_seconds > 0:
82
+ words_per_second = words / audio_duration_seconds
83
+ else:
84
+ words_per_second = 0
85
+ return {"words_per_second": words_per_second, "word_count": words}
86
+
87
+ def compare_pronunciation(self, transcript, reference_text):
88
+ phonetic_transcript = self.phonemizer.phonemize([transcript], strip=True)
89
+ phonetic_reference = self.phonemizer.phonemize([reference_text], strip=True)
90
+
91
+ def text_to_numeric(phonetic_text):
92
+ if not phonetic_text or not phonetic_text[0]:
93
+ return np.array([])
94
+ return np.array([ord(char) for char in phonetic_text[0]])
95
+
96
+ numeric_transcript = text_to_numeric(phonetic_transcript)
97
+ numeric_reference = text_to_numeric(phonetic_reference)
98
+
99
+ if numeric_transcript.size == 0 or numeric_reference.size == 0:
100
+ return {"phonetic_distance": float('inf')}
101
+
102
+ try:
103
+ dtw_distance, _ = fastdtw(numeric_transcript, numeric_reference, dist=lambda x, y: np.linalg.norm(x - y))
104
+ except ValueError: # Handle empty sequences for DTW
105
+ dtw_distance = float('inf')
106
+ return {"phonetic_distance": dtw_distance}
107
+
108
+ def evaluate_score(self, value, good_range, medium_range, is_lower_better=False):
109
+ """
110
+ Dəyəri yaxşı, orta, pis kateqoriyasına uyğun olaraq 1-10 arası bal verir.
111
+ 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.
112
+ """
113
+ if value is None or np.isnan(value) or value == float('inf'): # Handle invalid values
114
+ return "Hesaplanamadı", 0
115
+
116
+ if is_lower_better:
117
+ if value <= good_range[1]:
118
+ return "Yaxşı", 10
119
+ elif good_range[1] < value <= medium_range[1]:
120
+ return "Orta", 6
121
+ else:
122
+ return "Pis", 2
123
+ else:
124
+ if value >= good_range[0]:
125
+ return "Yaxşı", 10
126
+ elif medium_range[0] <= value < good_range[0]:
127
+ return "Orta", 6
128
+ else:
129
+ return "Pis", 2
130
+
131
+ def full_analysis(self, audio_data, reference_text):
132
+ # audio_data dictionary olarak gelmeli: {"raw": np.array, "sampling_rate": int}
133
+ y = audio_data["raw"]
134
+ sr = audio_data["sampling_rate"]
135
+
136
+ audio_duration = librosa.get_duration(y=y, sr=sr)
137
+
138
+ if asr_pipeline is None:
139
+ st.error("ASR pipeline yüklenemedi. Analiz yapılamıyor.")
140
+ return None
141
+
142
+ try:
143
+ transcript_result = asr_pipeline({"raw": y, "sampling_rate": sr})
144
+ transcript = transcript_result['text']
145
+ except Exception as e:
146
+ st.error(f"Transkripsiyon sırasında hata oluştu: {e}")
147
+ transcript = "" # Boş transkript ile devam et
148
+
149
+ prosody = self.analyze_prosody(y, sr)
150
+ fluency = self.analyze_speed_and_fluency(transcript, audio_duration)
151
+ pronunciation = self.compare_pronunciation(transcript, reference_text)
152
+
153
+ # Değerlendirme aralıkları: Bu aralıklar, modelinizin performansı ve istediğiniz metriklere göre ayarlanmalıdır.
154
+ # Bunlar sadece örneklerdir.
155
+ prosody_avg_pitch_eval = self.evaluate_score(prosody["average_pitch"], (100, 250), (70, 300), is_lower_better=False)
156
+ prosody_pitch_variance_eval = self.evaluate_score(prosody["pitch_variance"], (500, 3000), (200, 5000), is_lower_better=False)
157
+ # energy_peaks için audio_duration'a göre bir oran düşünebilirsiniz. Örneğin: 0.1-0.5 peak/saniye iyi olabilir.
158
+ 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)
159
+
160
+ fluency_wps_eval = self.evaluate_score(fluency["words_per_second"], (2.0, 3.0), (1.5, 3.5), is_lower_better=False)
161
+
162
+ pronunciation_phonetic_distance_eval = self.evaluate_score(pronunciation["phonetic_distance"], (0, 200), (201, 1000), is_lower_better=True)
163
+
164
+ return {
165
+ "transcript": transcript,
166
+ "prosody": {
167
+ "average_pitch": prosody["average_pitch"],
168
+ "average_pitch_score": prosody_avg_pitch_eval,
169
+ "pitch_variance": prosody["pitch_variance"],
170
+ "pitch_variance_score": prosody_pitch_variance_eval,
171
+ "energy_peaks": prosody["energy_peaks"],
172
+ "energy_peaks_score": prosody_energy_peaks_eval
173
+ },
174
+ "fluency": {
175
+ "words_per_second": fluency["words_per_second"],
176
+ "words_per_second_score": fluency_wps_eval,
177
+ "word_count": fluency["word_count"]
178
+ },
179
+ "pronunciation": {
180
+ "phonetic_distance": pronunciation["phonetic_distance"],
181
+ "phonetic_distance_score": pronunciation_phonetic_distance_eval
182
+ }
183
+ }
184
+
185
+ # Streamlit UI
186
+ st.title("Diksiyon Analiz Uygulaması")
187
+ st.write("Ses dosyanızı yükleyin ve referans metin girerek diksiyonunuzu analiz edin.")
188
+
189
+ uploaded_file = st.file_uploader("Ses Dosyası Yükleyin (.wav, .mp3)", type=["wav", "mp3"])
190
+ reference_text_input = st.text_area("Referans Metin Girin",
191
+ "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.")
192
+
193
+ if uploaded_file is not None and reference_text_input:
194
+ st.audio(uploaded_file, format='audio/wav') # Kullanıcının yüklediği sesi oynat
195
+
196
+ with st.spinner('Ses dosyası işleniyor...'):
197
+ try:
198
+ audio_bytes = uploaded_file.read()
199
+ audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes))
200
+
201
+ audio_array = np.array(audio_segment.get_array_of_samples(), dtype=np.float32)
202
+
203
+ # Ses stereo ise mono'ya çevir
204
+ if audio_segment.channels == 2:
205
+ audio_array = audio_array.reshape((-1, 2)).mean(axis=1)
206
+
207
+ # Normalizasyon
208
+ audio_array /= np.iinfo(audio_segment.array_type).max
209
+
210
+ # Eğer sample rate 16000 değilse, yeniden örnekle
211
+ if audio_segment.frame_rate != 16000:
212
+ st.info(f"Ses dosyası {audio_segment.frame_rate} Hz. 16000 Hz'e dönüştürülüyor...")
213
+ audio_array = librosa.resample(y=audio_array, orig_sr=audio_segment.frame_rate, target_sr=16000)
214
+
215
+ audio_input_for_analysis = {"raw": audio_array, "sampling_rate": 16000}
216
+
217
+ except Exception as e:
218
+ st.error(f"Ses dosyası okunurken veya işlenirken hata oluştu: {e}")
219
+ audio_input_for_analysis = None
220
+
221
+ if audio_input_for_analysis:
222
+ analyzer = DictionAnalyzer()
223
+
224
+ st.write("Analiz ediliyor, lütfen bekleyin...")
225
+ with st.spinner('Analiz tamamlanıyor...'):
226
+ report = analyzer.full_analysis(audio_input_for_analysis, reference_text_input)
227
+
228
+ if report:
229
+ st.subheader("Analiz Sonuçları")
230
+ st.write(f"**Transkript:** {report['transcript']}")
231
+
232
+ st.subheader("Prosoya Analizi")
233
+ st.write(f"Ortalama Ton: {report['prosody']['average_pitch']:.2f} Hz - **Kategori: {report['prosody']['average_pitch_score'][0]} ({report['prosody']['average_pitch_score'][1]}/10)**")
234
+ st.write(f"Ton Farkı: {report['prosody']['pitch_variance']:.2f} - **Kategori: {report['prosody']['pitch_variance_score'][0]} ({report['prosody']['pitch_variance_score'][1]}/10)**")
235
+ st.write(f"Enerji Pikleri: {report['prosody']['energy_peaks']} - **Kategori: {report['prosody']['energy_peaks_score'][0]} ({report['prosody']['energy_peaks_score'][1]}/10)** (Saniyedeki tepe noktası oranı)")
236
+
237
+ st.subheader("Sürat ve Akıcılık Analizi")
238
+ st.write(f"Saniyedeki Kelime Sayısı: {report['fluency']['words_per_second']:.2f} - **Kategori: {report['fluency']['words_per_second_score'][0]} ({report['fluency']['words_per_second_score'][1]}/10)**")
239
+ st.write(f"Toplam Kelime Sayısı: {report['fluency']['word_count']}")
240
 
241
+ st.subheader("Tələffüz Müqayisəsi")
242
+ st.write(f"Fonetik Məsafə (DTW): {report['pronunciation']['phonetic_distance']:.2f} - **Kategori: {report['pronunciation']['phonetic_distance_score'][0]} ({report['pronunciation']['phonetic_distance_score'][1]}/10)**")
243
+ else:
244
+ st.error("Analiz tamamlanamadı.")
245
+ elif uploaded_file is None and st.button("Analiz Et"):
246
+ st.warning("Lütfen bir ses dosyası yükleyin ve referans metin girin.")