File size: 5,179 Bytes
9fcaeae
 
1a2f7a1
 
 
 
9fcaeae
1a2f7a1
 
9fcaeae
1a2f7a1
 
 
 
 
 
 
 
 
 
 
9fcaeae
1a2f7a1
 
 
 
9fcaeae
1a2f7a1
9fcaeae
 
1a2f7a1
 
 
 
 
 
 
 
 
 
 
 
 
 
9fcaeae
1a2f7a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9fcaeae
1a2f7a1
 
 
 
 
 
1134cac
1a2f7a1
 
 
9fcaeae
1a2f7a1
 
9fcaeae
 
1a2f7a1
 
 
 
 
1134cac
1a2f7a1
 
 
 
 
 
9fcaeae
1a2f7a1
9fcaeae
1a2f7a1
 
 
 
9fcaeae
1a2f7a1
 
 
 
 
9fcaeae
1a2f7a1
 
 
9fcaeae
1a2f7a1
 
 
 
9fcaeae
1a2f7a1
 
9fcaeae
1a2f7a1
 
 
 
9fcaeae
1a2f7a1
09dbf8a
1a2f7a1
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import streamlit as st
import numpy as np
import librosa
import os
from sklearn.metrics.pairwise import cosine_similarity
# Tidak perlu import soundfile, librosa akan menggunakannya secara otomatis

# --- PATH KONFIGURASI ---
# Menggunakan path absolut untuk menemukan folder 'enroll'
APP_DIR = os.path.dirname(os.path.abspath(__file__))
ENROLL_DIR = os.path.join(APP_DIR, "enroll")

# ------------------------------
# Ekstraksi fitur suara (MFCC)
# ------------------------------
def extract_features(audio_source):
    """
    Kombinasi: Fungsi ini dari skrip Anda,
    tapi dimodifikasi untuk menerima 'audio_source' (file-like object)
    agar kita tidak perlu file temporer.
    """
    try:
        # librosa.load bisa membaca file-like object dari st.uploader
        y, sr = librosa.load(audio_source, sr=None)
        mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)
        return np.mean(mfcc.T, axis=0)
    except Exception as e:
        st.error(f"Gagal ekstrak fitur: {e}")
        return None

# ------------------------------
# Verifikasi identitas pengguna
# ------------------------------
def verify_user(audio_source, enroll_dir):
    """
    Logika dari skrip Anda, dikombinasikan dengan path yang lebih
    kuat (ENROLL_DIR) dan fungsi 'extract_features' yang
    sudah dimodifikasi.
    """
    test_feat = extract_features(audio_source)
    if test_feat is None:
        return None, 0.0

    similarities = {}
    
    if not os.path.isdir(enroll_dir):
        st.error(f"Folder 'enroll' tidak ditemukan di: {enroll_dir}")
        return None, 0.0

    for user in os.listdir(enroll_dir):
        user_dir = os.path.join(enroll_dir, user)
        if not os.path.isdir(user_dir):
            continue

        scores = []
        for file in os.listdir(user_dir):
            if file.endswith(".wav"):
                # Untuk file pendaftaran, kita gunakan file path (str)
                file_path = os.path.join(user_dir, file)
                feat = extract_features(file_path)
                
                if feat is not None:
                    # Logika 'cosine_similarity' Anda
                    sim = cosine_similarity([test_feat], [feat])[0][0]
                    scores.append(sim)
        if scores:
            similarities[user] = np.mean(scores)

    if not similarities:
        st.error("Tidak ada data pendaftaran (enroll) yang ditemukan/diproses.")
        return None, 0.0

    best_user = max(similarities, key=similarities.get)
    best_score = similarities[best_user]
    return best_user, best_score

# ------------------------------
# Deteksi kata kunci (buka/tutup) - Logika Anda
# ------------------------------
def detect_command(audio_source):
    """
    Ini adalah fungsi 'dummy' dari skrip Anda.
    Logikanya (berdasarkan durasi) dipertahankan.
    """
    try:
        y, sr = librosa.load(audio_source, sr=None)
        duration = librosa.get_duration(y=y, sr=sr)

        # Logika placeholder dari kode Anda:
        if duration < 1.0:
            text = "buka"
        else:
            text = "tutup"
            
        return text
    except Exception as e:
        # Kita sertakan nama file dalam error untuk debug
        st.error(f"Gagal deteksi perintah: {e}")
        return None

# ------------------------------
# UI Streamlit
# ------------------------------
st.title("πŸ” Sistem Verifikasi Suara - Perintah Buka/Tutup")
st.caption("Hanya pengguna terdaftar yang dapat memberikan perintah suara 'buka' atau 'tutup'.")

st.warning(
    """PERHATIAN: Deteksi perintah ('buka'/'tutup') saat ini 
    hanyalah **placeholder** berdasarkan durasi audio (audio pendek = 'buka', 
    audio panjang = 'tutup') dan **tidak akurat**.""", 
    icon="⚠️"
)

uploaded_file = st.file_uploader("πŸŽ™οΈ Unggah suara (.wav)", type=["wav"])

if uploaded_file is not None:
    # Kombinasi: Kita HAPUS 'tempfile' dan 'os.remove'.
    # Kita gunakan 'uploaded_file' secara langsung.
    st.audio(uploaded_file, format="audio/wav")

    if st.button("Mulai Verifikasi"):
        with st.spinner("Menganalisis suara..."):
            
            # PANGGILAN PERTAMA (memindahkan cursor ke akhir)
            user, score = verify_user(uploaded_file, ENROLL_DIR)

            # Logika UI Anda
            if user and score > 0.85:
                st.success(f"βœ… Pengguna terdeteksi: **{user}** (skor {score:.2f})")

                # === PERBAIKAN ===
                # Kembalikan cursor file ke awal sebelum membacanya lagi
                uploaded_file.seek(0)
                # =================

                # PANGGILAN KEDUA (sekarang berhasil)
                cmd = detect_command(uploaded_file)
                
                if cmd == "buka":
                    st.success("🟒 Perintah terdeteksi: **BUKA** β€” Sistem terbuka.")
                elif cmd == "tutup":
                    st.warning("πŸ”΄ Perintah terdeteksi: **TUTUP** β€” Sistem tertutup.")
                else:
                    st.info("⚠️ Tidak dapat mengenali perintah.")
            else:
                st.error("🚫 Akses ditolak! Suara tidak dikenali.")

# Tidak ada 'os.remove()' lagi karena tidak ada file temporer