almfz commited on
Commit
86dcc8f
Β·
verified Β·
1 Parent(s): c03589e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -0
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import torch
3
+ import torchaudio
4
+ from speechbrain.pretrained import EncoderClassifier, SpectralMaskEnhancement
5
+ from speechbrain.pretrained import KWS
6
+ import os
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+ import numpy as np
9
+
10
+ # --- Konfigurasi dan Pemuatan Model (Dijalankan sekali) ---
11
+
12
+ @st.cache_resource
13
+ def load_models():
14
+ """Memuat model verifikasi speaker dan KWS."""
15
+ # Model untuk Verifikasi Speaker (Tahap 1)
16
+ spk_model = EncoderClassifier.from_hparams(
17
+ source="speechbrain/spkrec-xvect-voxceleb",
18
+ savedir="pretrained_models/spkrec-xvect-voxceleb"
19
+ )
20
+
21
+ # Model untuk Deteksi Perintah (Tahap 2)
22
+ kws_model = KWS.from_hparams(
23
+ source="speechbrain/google_speech_command_xvector",
24
+ savedir="pretrained_models/google_speech_command_xvector"
25
+ )
26
+
27
+ # Model untuk membersihkan audio (Opsional tapi bagus)
28
+ enhancer = SpectralMaskEnhancement.from_hparams(
29
+ source="speechbrain/metricgan-plus-voicebank",
30
+ savedir="pretrained_models/metricgan-plus-voicebank"
31
+ )
32
+ return spk_model, kws_model, enhancer
33
+
34
+ # Memuat model
35
+ spk_model, kws_model, enhancer = load_models()
36
+
37
+ # Direktori pendaftaran
38
+ ENROLL_DIR = "enroll/"
39
+ THRESHOLD = 0.85 # Ambang batas kemiripan
40
+
41
+ # --- Fungsi Helper ---
42
+
43
+ def preprocess_audio(wav_file):
44
+ """Memuat, membersihkan, dan mengubah sample rate audio."""
45
+ try:
46
+ # Muat audio dari file yang di-upload
47
+ sig, fs = torchaudio.load(wav_file)
48
+
49
+ # Bersihkan noise (jika model enhancer ada)
50
+ if enhancer:
51
+ enhanced_sig = enhancer.enhance_batch(sig, lengths=torch.tensor([sig.shape[1]]))
52
+ sig = enhanced_sig.squeeze(0)
53
+
54
+ # Resample ke 16kHz (wajib untuk model)
55
+ if fs != 16000:
56
+ resampler = torchaudio.transforms.Resample(orig_freq=fs, new_freq=16000)
57
+ sig = resampler(sig)
58
+
59
+ return sig
60
+ except Exception as e:
61
+ st.error(f"Error memproses audio: {e}")
62
+ return None
63
+
64
+ @st.cache_data
65
+ def get_enrollment_embeddings():
66
+ """
67
+ Membuat embedding (sidik jari suara) rata-rata
68
+ untuk setiap pengguna di folder /enroll.
69
+ """
70
+ enrollment_data = {}
71
+ if not os.path.exists(ENROLL_DIR):
72
+ st.warning(f"Folder '{ENROLL_DIR}' tidak ditemukan.")
73
+ return {}
74
+
75
+ for speaker_name in os.listdir(ENROLL_DIR):
76
+ speaker_dir = os.path.join(ENROLL_DIR, speaker_name)
77
+ if os.path.isdir(speaker_dir):
78
+ embeddings = []
79
+ for wav_file in os.listdir(speaker_dir):
80
+ if wav_file.endswith(".wav"):
81
+ wav_path = os.path.join(speaker_dir, wav_file)
82
+ try:
83
+ sig, fs = torchaudio.load(wav_path)
84
+ if fs != 16000:
85
+ resampler = torchaudio.transforms.Resample(orig_freq=fs, new_freq=16000)
86
+ sig = resampler(sig)
87
+
88
+ # Buat embedding
89
+ with torch.no_grad():
90
+ emb = spk_model.encode_batch(sig)
91
+ emb = emb.squeeze()
92
+ embeddings.append(emb.numpy())
93
+ except Exception as e:
94
+ st.error(f"Gagal memproses {wav_path}: {e}")
95
+
96
+ if embeddings:
97
+ # Ambil rata-rata embedding untuk speaker ini
98
+ enrollment_data[speaker_name] = np.mean(embeddings, axis=0)
99
+
100
+ return enrollment_data
101
+
102
+ # --- Antarmuka Streamlit ---
103
+ st.title("Sistem Verifikasi Perintah Suara πŸ”")
104
+ st.write("Unggah file .wav untuk verifikasi.")
105
+
106
+ # Muat data pendaftaran
107
+ enrollment_embeddings = get_enrollment_embeddings()
108
+
109
+ if not enrollment_embeddings:
110
+ st.error("Tidak ada data pendaftaran yang ditemukan. Pastikan folder 'enroll' ada dan berisi file .wav.")
111
+ else:
112
+ st.success(f"Berhasil memuat data pendaftaran untuk: {list(enrollment_embeddings.keys())}")
113
+
114
+ uploaded_file = st.file_uploader("Pilih file audio...", type=["wav"])
115
+
116
+ if uploaded_file is not None:
117
+ st.audio(uploaded_file, format="audio/wav")
118
+
119
+ if st.button("Verifikasi Sekarang"):
120
+ with st.spinner("Memproses audio..."):
121
+ signal = preprocess_audio(uploaded_file)
122
+
123
+ if signal is not None:
124
+ # --- TAHAP 1: VERIFIKASI SPEAKER (SIAPA?) ---
125
+ st.subheader("Tahap 1: Verifikasi Speaker")
126
+
127
+ with torch.no_grad():
128
+ upload_embedding = spk_model.encode_batch(signal).squeeze().numpy()
129
+
130
+ best_score = 0
131
+ best_match = "Tidak Dikenali"
132
+
133
+ # Bandingkan dengan setiap speaker yang terdaftar
134
+ for speaker_name, enrolled_emb in enrollment_embeddings.items():
135
+ score = cosine_similarity(
136
+ upload_embedding.reshape(1, -1),
137
+ enrolled_emb.reshape(1, -1)
138
+ )[0][0]
139
+
140
+ st.write(f"Skor kemiripan dengan {speaker_name}: **{score:.2f}**")
141
+
142
+ if score > best_score:
143
+ best_score = score
144
+ best_match = speaker_name
145
+
146
+ # --- KEPUTUSAN TAHAP 1 ---
147
+ if best_score > THRESHOLD:
148
+ st.success(f"βœ… **Akses Diberikan**: Dikenali sebagai **{best_match}** (Skor: {best_score:.2f})")
149
+
150
+ # --- TAHAP 2: DETEKSI PERINTAH (APA?) ---
151
+ st.subheader("Tahap 2: Deteksi Perintah")
152
+ with st.spinner("Mendeteksi perintah..."):
153
+ with torch.no_grad():
154
+ # Model KWS memprediksi probabilitas
155
+ prediction = kws_model.classify_batch(signal)
156
+
157
+ # Ambil label dengan probabilitas tertinggi
158
+ # 'prediction[0]' adalah tensor probabilitas
159
+ # 'prediction[3]' adalah labelnya
160
+ top_prob = torch.max(prediction[0]).item()
161
+ top_label = prediction[3][0]
162
+
163
+ # Logika untuk perintah "Buka" (Up) atau "Tutup" (Down)
164
+ # Catatan: Sesuaikan label ini ("Up", "Down") dengan output model KWS Anda
165
+ # Model Google Speech Command menggunakan "Up", "Down", "Left", "Right", "Yes", "No", dll.
166
+
167
+ st.write(f"Perintah terdeteksi: **{top_label}** (Keyakinan: {top_prob:.2f})")
168
+
169
+ if top_label.lower() == "up": # Asumsikan 'Up' = 'Buka'
170
+ st.balloons()
171
+ st.success(f"πŸ”“ **Perintah Diterima**: `{best_match}` berkata 'BUKA'.")
172
+ elif top_label.lower() == "down": # Asumsikan 'Down' = 'Tutup'
173
+ st.success(f"πŸ”’ **Perintah Diterima**: `{best_match}` berkata 'TUTUP'.")
174
+ else:
175
+ st.warning(f"Perintah '{top_label}' tidak dikenali sebagai 'Buka' atau 'Tutup'.")
176
+
177
+ else:
178
+ st.error(f"❌ **Akses Ditolak**: Suara tidak dikenali (Skor tertinggi: {best_score:.2f})")