Diction_analysis / src /streamlit_app.py
Ilkinism's picture
Update src/streamlit_app.py
dca1e83 verified
import streamlit as st
import torch
import numpy as np
from pydub import AudioSegment
from transformers import pipeline, WhisperProcessor
# webrtcvad: Bu kütüphane genel VAD için kullanılır ancak mevcut analiz akışınızda doğrudan bir işlevi yok.
# Eğer kullanmayacaksanız requirements.txt'den ve importtan kaldırabilirsiniz.
import webrtcvad
from phonemizer.backend import EspeakBackend
from fastdtw import fastdtw
from scipy.signal import find_peaks
import librosa
import io
import os # Dosya yollarını yönetmek için
# Model ve tokenizer yolları (yerel klasörler için göreceli yollar)
# Hugging Face Spaces'te bu klasörler projenizin kök dizininde olacaktır.
model_path = './model_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab/'
tokenizer_path = './tokenizer_whisper-large-v3-turbo-aze-60hours(part15_aug)-lab/'
# Model ve tokenizer klasörlerinin varlığını kontrol et
if not os.path.exists(model_path) or not os.path.exists(tokenizer_path):
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.")
st.stop() # Uygulamayı durdur
@st.cache_resource
def load_asr_components():
"""ASR modelini ve işlemcisini önbelleğe alır."""
try:
processor = WhisperProcessor.from_pretrained(tokenizer_path, language='aze', task='transcribe')
pipeline_config = {
"task": "automatic-speech-recognition",
"model": model_path,
"tokenizer": processor.tokenizer,
"feature_extractor": processor.feature_extractor,
"device": 0 if torch.cuda.is_available() else -1, # CUDA varsa GPU kullan
"torch_dtype": torch.float16 if torch.cuda.is_available() else torch.float32,
"generate_kwargs": {
"task": "transcribe",
"language": "azerbaijani",
"num_beams": 2,
"condition_on_prev_tokens": False,
"compression_ratio_threshold": 2.4,
"temperature": (0.0, 0.2, 0.4),
"logprob_threshold": -1.0,
"return_timestamps": True,
}
}
asr_pipeline = pipeline(**pipeline_config)
return processor, asr_pipeline
except Exception as e:
st.error(f"ASR bileşenleri yüklenirken hata oluştu: {e}. Lütfen model ve tokenizer yollarını kontrol edin.")
return None, None
processor, asr_pipeline = load_asr_components()
class DictionAnalyzer:
def __init__(self):
# phonemizer backend'i de önbelleğe alınabilir veya burada başlatılabilir
# EspeakBackend'in başlatılması biraz zaman alabilir, ilk yüklemede sorun olmaması için burada.
self.phonemizer = EspeakBackend("az")
def analyze_prosody(self, y, sr):
try:
pitch, _, _ = librosa.pyin(y, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'))
except Exception:
pitch = np.array([np.nan]) # Handle cases where pyin might fail
energy = librosa.feature.rms(y=y)[0]
peaks, _ = find_peaks(energy, height=np.mean(energy) if energy.size > 0 else 0)
return {
"average_pitch": np.nanmean(pitch) if not np.all(np.isnan(pitch)) else 0,
"pitch_variance": np.nanvar(pitch) if not np.all(np.isnan(pitch)) else 0,
"energy_peaks": len(peaks)
}
def analyze_speed_and_fluency(self, transcript, audio_duration_seconds):
words = len(transcript.split())
if audio_duration_seconds > 0:
words_per_second = words / audio_duration_seconds
else:
words_per_second = 0
return {"words_per_second": words_per_second, "word_count": words}
def compare_pronunciation(self, transcript, reference_text):
phonetic_transcript = self.phonemizer.phonemize([transcript], strip=True)
phonetic_reference = self.phonemizer.phonemize([reference_text], strip=True)
def text_to_numeric(phonetic_text):
if not phonetic_text or not phonetic_text[0]:
return np.array([])
return np.array([ord(char) for char in phonetic_text[0]])
numeric_transcript = text_to_numeric(phonetic_transcript)
numeric_reference = text_to_numeric(phonetic_reference)
if numeric_transcript.size == 0 or numeric_reference.size == 0:
return {"phonetic_distance": float('inf')}
try:
dtw_distance, _ = fastdtw(numeric_transcript, numeric_reference, dist=lambda x, y: np.linalg.norm(x - y))
except ValueError: # Handle empty sequences for DTW
dtw_distance = float('inf')
return {"phonetic_distance": dtw_distance}
def evaluate_score(self, value, good_range, medium_range, is_lower_better=False):
"""
Dəyəri yaxşı, orta, pis kateqoriyasına uyğun olaraq 1-10 arası bal verir.
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.
"""
if value is None or np.isnan(value) or value == float('inf'): # Handle invalid values
return "Hesaplanamadı", 0
if is_lower_better:
if value <= good_range[1]:
return "Yaxşı", 10
elif good_range[1] < value <= medium_range[1]:
return "Orta", 6
else:
return "Pis", 2
else:
if value >= good_range[0]:
return "Yaxşı", 10
elif medium_range[0] <= value < good_range[0]:
return "Orta", 6
else:
return "Pis", 2
def full_analysis(self, audio_data, reference_text):
# audio_data dictionary olarak gelmeli: {"raw": np.array, "sampling_rate": int}
y = audio_data["raw"]
sr = audio_data["sampling_rate"]
audio_duration = librosa.get_duration(y=y, sr=sr)
if asr_pipeline is None:
st.error("ASR pipeline yüklenemedi. Analiz yapılamıyor.")
return None
try:
transcript_result = asr_pipeline({"raw": y, "sampling_rate": sr})
transcript = transcript_result['text']
except Exception as e:
st.error(f"Transkripsiyon sırasında hata oluştu: {e}")
transcript = "" # Boş transkript ile devam et
prosody = self.analyze_prosody(y, sr)
fluency = self.analyze_speed_and_fluency(transcript, audio_duration)
pronunciation = self.compare_pronunciation(transcript, reference_text)
# Değerlendirme aralıkları: Bu aralıklar, modelinizin performansı ve istediğiniz metriklere göre ayarlanmalıdır.
# Bunlar sadece örneklerdir.
prosody_avg_pitch_eval = self.evaluate_score(prosody["average_pitch"], (100, 250), (70, 300), is_lower_better=False)
prosody_pitch_variance_eval = self.evaluate_score(prosody["pitch_variance"], (500, 3000), (200, 5000), is_lower_better=False)
# energy_peaks için audio_duration'a göre bir oran düşünebilirsiniz. Örneğin: 0.1-0.5 peak/saniye iyi olabilir.
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)
fluency_wps_eval = self.evaluate_score(fluency["words_per_second"], (2.0, 3.0), (1.5, 3.5), is_lower_better=False)
pronunciation_phonetic_distance_eval = self.evaluate_score(pronunciation["phonetic_distance"], (0, 200), (201, 1000), is_lower_better=True)
return {
"transcript": transcript,
"prosody": {
"average_pitch": prosody["average_pitch"],
"average_pitch_score": prosody_avg_pitch_eval,
"pitch_variance": prosody["pitch_variance"],
"pitch_variance_score": prosody_pitch_variance_eval,
"energy_peaks": prosody["energy_peaks"],
"energy_peaks_score": prosody_energy_peaks_eval
},
"fluency": {
"words_per_second": fluency["words_per_second"],
"words_per_second_score": fluency_wps_eval,
"word_count": fluency["word_count"]
},
"pronunciation": {
"phonetic_distance": pronunciation["phonetic_distance"],
"phonetic_distance_score": pronunciation_phonetic_distance_eval
}
}
# Streamlit UI
st.title("Diksiyon Analiz Uygulaması")
st.write("Ses dosyanızı yükleyin ve referans metin girerek diksiyonunuzu analiz edin.")
uploaded_file = st.file_uploader("Ses Dosyası Yükleyin (.wav, .mp3)", type=["wav", "mp3"])
reference_text_input = st.text_area("Referans Metin Girin",
"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.")
if uploaded_file is not None and reference_text_input:
st.audio(uploaded_file, format='audio/wav') # Kullanıcının yüklediği sesi oynat
with st.spinner('Ses dosyası işleniyor...'):
try:
audio_bytes = uploaded_file.read()
audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes))
audio_array = np.array(audio_segment.get_array_of_samples(), dtype=np.float32)
# Ses stereo ise mono'ya çevir
if audio_segment.channels == 2:
audio_array = audio_array.reshape((-1, 2)).mean(axis=1)
# Normalizasyon
audio_array /= np.iinfo(audio_segment.array_type).max
# Eğer sample rate 16000 değilse, yeniden örnekle
if audio_segment.frame_rate != 16000:
st.info(f"Ses dosyası {audio_segment.frame_rate} Hz. 16000 Hz'e dönüştürülüyor...")
audio_array = librosa.resample(y=audio_array, orig_sr=audio_segment.frame_rate, target_sr=16000)
audio_input_for_analysis = {"raw": audio_array, "sampling_rate": 16000}
except Exception as e:
st.error(f"Ses dosyası okunurken veya işlenirken hata oluştu: {e}")
audio_input_for_analysis = None
if audio_input_for_analysis:
analyzer = DictionAnalyzer()
st.write("Analiz ediliyor, lütfen bekleyin...")
with st.spinner('Analiz tamamlanıyor...'):
report = analyzer.full_analysis(audio_input_for_analysis, reference_text_input)
if report:
st.subheader("Analiz Sonuçları")
st.write(f"**Transkript:** {report['transcript']}")
st.subheader("Prosoya Analizi")
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)**")
st.write(f"Ton Farkı: {report['prosody']['pitch_variance']:.2f} - **Kategori: {report['prosody']['pitch_variance_score'][0]} ({report['prosody']['pitch_variance_score'][1]}/10)**")
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ı)")
st.subheader("Sürat ve Akıcılık Analizi")
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)**")
st.write(f"Toplam Kelime Sayısı: {report['fluency']['word_count']}")
st.subheader("Tələffüz Müqayisəsi")
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)**")
else:
st.error("Analiz tamamlanamadı.")
elif uploaded_file is None and st.button("Analiz Et"):
st.warning("Lütfen bir ses dosyası yükleyin ve referans metin girin.")