almfz commited on
Commit
1a2f7a1
Β·
verified Β·
1 Parent(s): 9b06b18

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -193
app.py CHANGED
@@ -1,217 +1,148 @@
1
  import streamlit as st
2
- import torch
3
- import os
4
- import librosa
5
  import numpy as np
6
- import re
7
- from speechbrain.inference.speaker import SpeakerRecognition
8
- from transformers import pipeline
9
-
10
- # --- KONFIGURASI APLIKASI ---
11
- st.set_page_config(page_title="Verifikasi Suara HF", layout="centered")
12
- st.title("πŸ” Sistem Verifikasi Perintah Suara (Full HF)")
13
- st.write("Aplikasi ini menggunakan ASR (Whisper) untuk deteksi perintah dan Verifikasi Suara (SpeechBrain) untuk otentikasi.")
14
 
15
- # --- PATH & PENGATURAN MODEL ---
 
16
  APP_DIR = os.path.dirname(os.path.abspath(__file__))
17
-
18
- # === PASTIKAN NAMA FOLDER INI SUDAH BENAR ===
19
- PATH_ANDA = os.path.join(APP_DIR, "enroll", "v_ilham")
20
- PATH_TEMAN = os.path.join(APP_DIR, "enroll", "v_danendra")
21
- # ============================================
22
-
23
- THRESHOLD = 0.85 # Sesuaikan ini jika perlu
24
-
25
- # --- FUNGSI BANTUAN MODEL (SpeechBrain - Verifikasi Suara) ---
26
-
27
- def get_embedding(audio_source, model_sv):
28
- """Menerima file path (str) ATAU file-like object."""
29
  try:
30
- embedding = model_sv.encode_file(audio_source)
31
- return embedding.squeeze()
 
 
32
  except Exception as e:
33
- st.error(f"Error processing audio source for SV: {e}")
34
  return None
35
 
36
- def get_similarity(emb1, emb2, model_sv):
37
- emb1_batch = emb1.unsqueeze(0)
38
- emb2_batch = emb2.unsqueeze(0)
39
- score = model_sv.similarity(emb1_batch, emb2_batch)
40
- return score.item()
41
-
42
- def create_master_voiceprint(directory_path, model_sv):
43
- embeddings = []
44
- if not os.path.isdir(directory_path):
45
- st.warning(f"Direktori pendaftaran tidak ditemukan: {directory_path}")
46
- return None
47
-
48
- files_found = 0
49
- for file_name in os.listdir(directory_path):
50
- if file_name.endswith(".wav"):
51
- files_found += 1
52
- file_path = os.path.join(directory_path, file_name)
53
- emb = get_embedding(file_path, model_sv)
54
- if emb is not None:
55
- embeddings.append(emb)
56
 
57
- if files_found == 0:
58
- st.error(f"Tidak ada file .wav ditemukan di {directory_path}")
59
- return None
60
- if not embeddings:
61
- st.error(f"Gagal memproses file .wav di {directory_path}.")
62
- return None
63
-
64
- master_voiceprint = torch.mean(torch.stack(embeddings), dim=0)
65
- return master_voiceprint
66
-
67
- # --- LOADING MODEL (DENGAN CACHE) ---
68
-
69
- @st.cache_resource
70
- def load_model_sv():
71
- st.info("Memuat Model Verifikasi Suara (SpeechBrain)...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  try:
73
- # === PERBAIKAN: Menyederhanakan Panggilan ===
74
- # Kita TIDAK akan meneruskan argumen 'token' atau 'use_auth_token'.
75
- # Kita biarkan library 'huggingface_hub' secara otomatis
76
- # menemukan HF_TOKEN Anda dari environment secrets.
77
-
78
- # 1. Pastikan token ada (untuk debug)
79
- hf_token = os.environ.get("HF_TOKEN")
80
- if hf_token is None:
81
- st.warning("HF_TOKEN secret tidak ditemukan. Unduhan mungkin gagal.")
82
  else:
83
- st.info("HF_TOKEN ditemukan. Melanjutkan dengan autentikasi...")
84
-
85
- # 2. Panggil model TANPA argumen token.
86
- # Kita kembali ke model 'ecapa-tdnn' yang asli (yang Gated).
87
- model = SpeakerRecognition.from_hparams(
88
- source="speechbrain/spkrec-ecapa-tdnn",
89
- savedir="pretrained_models/spkrec-ecapa-tdnn"
90
- # Perhatikan: TIDAK ADA 'token=hf_token' di sini.
91
- )
92
- # =============================================
93
-
94
- st.success("Model Verifikasi Suara siap.")
95
- return model
96
  except Exception as e:
97
- st.exception(e)
98
- st.error("Gagal memuat model SpeechBrain.")
99
  return None
100
 
101
- # ... sisa kode Anda (load_model_asr, dll. biarkan apa adanya) ...
 
 
 
 
102
 
103
- @st.cache_resource
104
- def load_model_asr():
105
- st.info("Memuat Model ASR (Whisper - Hugging Face)...")
106
- try:
107
- asr_pipeline = pipeline(
108
- "automatic-speech-recognition",
109
- model="openai/whisper-tiny"
110
- )
111
- st.success("Model ASR (Whisper) siap.")
112
- return asr_pipeline
113
- except Exception as e:
114
- st.exception(e)
115
- st.error("Gagal memuat model ASR.")
116
- return None
117
 
118
- @st.cache_resource
119
- def load_voiceprints(_model_sv):
120
- st.info("Membuat master voiceprint...")
121
- if _model_sv is None:
122
- st.error("Model SV (SpeechBrain) gagal di-load, tidak bisa membuat voiceprint.")
123
- return None
124
-
125
- voiceprints = {}
126
- vp_a = create_master_voiceprint(PATH_ANDA, _model_sv)
127
- if vp_a is not None:
128
- voiceprints["anda"] = vp_a
129
- st.success("Voiceprint 'anda' dibuat.")
130
-
131
- vp_b = create_master_voiceprint(PATH_TEMAN, _model_sv)
132
- if vp_b is not None:
133
- voiceprints["teman"] = vp_b
134
- st.success("Voiceprint 'teman' dibuat.")
135
-
136
- if not voiceprints:
137
- st.error("Gagal membuat voiceprint.")
138
- return None
139
- return voiceprints
140
 
141
- # --- FUNGSI PIPELINE UTAMA ---
 
 
 
142
 
143
- def transkripsi_audio(audio_source, asr_pipeline):
144
- st.info("Mentranskripsi audio (ASR)...")
145
- try:
146
- audio_data, sr = librosa.load(audio_source, sr=16000)
147
- hasil = asr_pipeline(audio_data)
148
- teks = hasil["text"].strip().lower()
149
- teks_bersih = re.sub(r'[^\w\s]', '', teks).strip()
150
- st.info(f"Teks terdeteksi: **'{teks}'** (Dibersihkan: **'{teks_bersih}'**)")
151
- return teks_bersih
152
- except Exception as e:
153
- st.exception(e)
154
- st.error("Gagal mentranskripsi audio.")
155
- return None
156
 
157
- def verifikasi_suara(audio_source, model_sv, voiceprints, threshold):
158
- test_embedding = get_embedding(audio_source, model_sv)
159
- if test_embedding is None:
160
- return False, 0.0, "Gagal buat embedding"
161
 
162
- best_score = -1.0
163
- best_match = "None"
164
-
165
- for name, master_vp in voiceprints.items():
166
- score = get_similarity(test_embedding, master_vp, model_sv)
167
- if score > best_score:
168
- best_score = score
169
- best_match = name
170
-
171
- if best_score >= threshold:
172
- return True, best_score, best_match
173
- else:
174
- return False, best_score, "None"
175
-
176
- # --- MAIN APP ---
177
- model_sv = load_model_sv()
178
- model_asr = load_model_asr()
179
- voiceprints = load_voiceprints(model_sv)
180
-
181
- if not all([model_sv, model_asr, voiceprints]):
182
- st.error("Gagal memuat semua model/voiceprint. Aplikasi tidak bisa berjalan. Cek error di atas.")
183
- else:
184
- st.header("Upload Audio Perintah (.wav)")
185
- uploaded_file = st.file_uploader("Pilih file audio...", type=["wav"])
186
-
187
- if uploaded_file is not None:
188
- st.audio(uploaded_file, format="audio/wav")
189
 
190
- if st.button("Proses Perintah", disabled=(uploaded_file is None)):
191
-
192
- with st.spinner("Menganalisis audio..."):
193
-
194
- st.subheader("Hasil Model 1: Deteksi Kata Kunci (ASR)")
195
- kata_kunci = transkripsi_audio(uploaded_file, model_asr)
196
-
197
- if kata_kunci in ["buka", "tutup"]:
198
- st.success(f"Kata kunci terdeteksi: **{kata_kunci.upper()}**")
199
-
200
- st.subheader("Hasil Model 2: Verifikasi Suara (SpeechBrain)")
201
- terverifikasi, skor, nama = verifikasi_suara(
202
- uploaded_file, model_sv, voiceprints, THRESHOLD
203
- )
204
 
205
- st.info(f"Skor kemiripan tertinggi: **{skor:.2%}** (dengan '{nama}')")
206
-
207
- st.header("Keputusan Akhir")
208
- if terverifikasi:
209
- st.success(f"βœ… DITERIMA. Suara terverifikasi sebagai '{nama}'. Perintah **{kata_kunci.upper()}** dijalankan.")
210
  else:
211
- st.error(f"❌ DITOLAK. Suara tidak dikenal. Perintah **{kata_kunci.upper()}** dibatalkan.")
212
-
213
- elif kata_kunci is None:
214
- st.error("Terjadi error saat memproses kata kunci (ASR).")
215
  else:
216
- st.header("Keputusan Akhir")
217
- st.warning(f"❌ DITOLAK. Perintah tidak dikenal (terdeteksi sebagai: '{kata_kunci}').")
 
 
1
  import streamlit as st
 
 
 
2
  import numpy as np
3
+ import librosa
4
+ import os
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ # Tidak perlu import soundfile, librosa akan menggunakannya secara otomatis
 
 
 
 
7
 
8
+ # --- PATH KONFIGURASI ---
9
+ # Menggunakan path absolut untuk menemukan folder 'enroll'
10
  APP_DIR = os.path.dirname(os.path.abspath(__file__))
11
+ ENROLL_DIR = os.path.join(APP_DIR, "enroll")
12
+
13
+ # ------------------------------
14
+ # Ekstraksi fitur suara (MFCC)
15
+ # ------------------------------
16
+ def extract_features(audio_source):
17
+ """
18
+ Kombinasi: Fungsi ini dari skrip Anda,
19
+ tapi dimodifikasi untuk menerima 'audio_source' (file-like object)
20
+ agar kita tidak perlu file temporer.
21
+ """
 
22
  try:
23
+ # librosa.load bisa membaca file-like object dari st.uploader
24
+ y, sr = librosa.load(audio_source, sr=None)
25
+ mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)
26
+ return np.mean(mfcc.T, axis=0)
27
  except Exception as e:
28
+ st.error(f"Gagal ekstrak fitur: {e}")
29
  return None
30
 
31
+ # ------------------------------
32
+ # Verifikasi identitas pengguna
33
+ # ------------------------------
34
+ def verify_user(audio_source, enroll_dir):
35
+ """
36
+ Logika dari skrip Anda, dikombinasikan dengan path yang lebih
37
+ kuat (ENROLL_DIR) dan fungsi 'extract_features' yang
38
+ sudah dimodifikasi.
39
+ """
40
+ test_feat = extract_features(audio_source)
41
+ if test_feat is None:
42
+ return None, 0.0
43
+
44
+ similarities = {}
 
 
 
 
 
 
45
 
46
+ if not os.path.isdir(enroll_dir):
47
+ st.error(f"Folder 'enroll' tidak ditemukan di: {enroll_dir}")
48
+ return None, 0.0
49
+
50
+ for user in os.listdir(enroll_dir):
51
+ user_dir = os.path.join(enroll_dir, user)
52
+ if not os.path.isdir(user_dir):
53
+ continue
54
+
55
+ scores = []
56
+ for file in os.listdir(user_dir):
57
+ if file.endswith(".wav"):
58
+ # Untuk file pendaftaran, kita gunakan file path (str)
59
+ file_path = os.path.join(user_dir, file)
60
+ feat = extract_features(file_path)
61
+
62
+ if feat is not None:
63
+ # Logika 'cosine_similarity' Anda
64
+ sim = cosine_similarity([test_feat], [feat])[0][0]
65
+ scores.append(sim)
66
+ if scores:
67
+ similarities[user] = np.mean(scores)
68
+
69
+ if not similarities:
70
+ st.error("Tidak ada data pendaftaran (enroll) yang ditemukan/diproses.")
71
+ return None, 0.0
72
+
73
+ best_user = max(similarities, key=similarities.get)
74
+ best_score = similarities[best_user]
75
+ return best_user, best_score
76
+
77
+ # ------------------------------
78
+ # Deteksi kata kunci (buka/tutup) - Logika Anda
79
+ # ------------------------------
80
+ def detect_command(audio_source):
81
+ """
82
+ Ini adalah fungsi 'dummy' dari skrip Anda.
83
+ Logikanya (berdasarkan durasi) dipertahankan.
84
+ """
85
  try:
86
+ y, sr = librosa.load(audio_source, sr=None)
87
+ duration = librosa.get_duration(y=y, sr=sr)
88
+
89
+ # Logika placeholder dari kode Anda:
90
+ if duration < 1.0:
91
+ text = "buka"
 
 
 
92
  else:
93
+ text = "tutup"
94
+
95
+ return text
 
 
 
 
 
 
 
 
 
 
96
  except Exception as e:
97
+ # Kita sertakan nama file dalam error untuk debug
98
+ st.error(f"Gagal deteksi perintah: {e}")
99
  return None
100
 
101
+ # ------------------------------
102
+ # UI Streamlit
103
+ # ------------------------------
104
+ st.title("πŸ” Sistem Verifikasi Suara - Perintah Buka/Tutup")
105
+ st.caption("Hanya pengguna terdaftar yang dapat memberikan perintah suara 'buka' atau 'tutup'.")
106
 
107
+ st.warning(
108
+ """PERHATIAN: Deteksi perintah ('buka'/'tutup') saat ini
109
+ hanyalah **placeholder** berdasarkan durasi audio (audio pendek = 'buka',
110
+ audio panjang = 'tutup') dan **tidak akurat**.""",
111
+ icon="⚠️"
112
+ )
 
 
 
 
 
 
 
 
113
 
114
+ uploaded_file = st.file_uploader("πŸŽ™οΈ Unggah suara (.wav)", type=["wav"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ if uploaded_file is not None:
117
+ # Kombinasi: Kita HAPUS 'tempfile' dan 'os.remove'.
118
+ # Kita gunakan 'uploaded_file' secara langsung.
119
+ st.audio(uploaded_file, format="audio/wav")
120
 
121
+ if st.button("Mulai Verifikasi"):
122
+ with st.spinner("Menganalisis suara..."):
123
+
124
+ # PANGGILAN PERTAMA (memindahkan cursor ke akhir)
125
+ user, score = verify_user(uploaded_file, ENROLL_DIR)
 
 
 
 
 
 
 
 
126
 
127
+ # Logika UI Anda
128
+ if user and score > 0.85:
129
+ st.success(f"βœ… Pengguna terdeteksi: **{user}** (skor {score:.2f})")
 
130
 
131
+ # === PERBAIKAN ===
132
+ # Kembalikan cursor file ke awal sebelum membacanya lagi
133
+ uploaded_file.seek(0)
134
+ # =================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ # PANGGILAN KEDUA (sekarang berhasil)
137
+ cmd = detect_command(uploaded_file)
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ if cmd == "buka":
140
+ st.success("🟒 Perintah terdeteksi: **BUKA** β€” Sistem terbuka.")
141
+ elif cmd == "tutup":
142
+ st.warning("πŸ”΄ Perintah terdeteksi: **TUTUP** β€” Sistem tertutup.")
 
143
  else:
144
+ st.info("⚠️ Tidak dapat mengenali perintah.")
 
 
 
145
  else:
146
+ st.error("🚫 Akses ditolak! Suara tidak dikenali.")
147
+
148
+ # Tidak ada 'os.remove()' lagi karena tidak ada file temporer