seanerons commited on
Commit
9401dac
Β·
verified Β·
1 Parent(s): 27468f5

Rename src/streamlit_app.py to src/app.py

Browse files
Files changed (2) hide show
  1. src/app.py +204 -0
  2. src/streamlit_app.py +0 -40
src/app.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import streamlit as st
4
+ import soundfile as sf
5
+ import librosa
6
+
7
+ from yt_dlp import YoutubeDL
8
+ from moviepy.editor import VideoFileClip
9
+
10
+ import whisper
11
+ import whisper.tokenizer as tok
12
+ from speechbrain.pretrained import EncoderClassifier
13
+ import numpy as np
14
+ from audio_recorder_streamlit import audio_recorder
15
+
16
+ # ───────────────────────────────────────────────
17
+ # 1) Page config & Dark Theme Styling
18
+ # ───────────────────────────────────────────────
19
+ st.set_page_config(page_title="English & Accent Detector", page_icon="🎀", layout="wide")
20
+ st.markdown("""
21
+ <style>
22
+ body, .stApp { background-color: #121212; color: #e0e0e0; overflow-y: scroll; }
23
+ .stButton>button {
24
+ background-color: #1f77b4; color: #fff;
25
+ border-radius:8px; padding:0.6em 1.2em; font-size:1rem;
26
+ }
27
+ .stButton>button:hover { background-color: #105b88; }
28
+ .stVideo > video { max-width: 300px !important; border: 1px solid #333; }
29
+ </style>
30
+ """, unsafe_allow_html=True)
31
+
32
+ # ───────────────────────────────────────────────
33
+ # 2) Load models once
34
+ # ───────────────────────────────────────────────
35
+ wmodel = whisper.load_model("tiny")
36
+ classifier = EncoderClassifier.from_hparams(
37
+ source="Jzuluaga/accent-id-commonaccent_ecapa",
38
+ savedir="pretrained_models/accent-id-commonaccent_ecapa"
39
+ )
40
+
41
+ # ───────────────────────────────────────────────
42
+ # 3) Accent grouping map
43
+ # ───────────────────────────────────────────────
44
+ GROUP_MAP = {
45
+ "england": "British", "us": "American", "canada": "American",
46
+ "australia": "Australian", "newzealand": "Australian",
47
+ "indian": "Indian", "scotland": "Scottish", "ireland": "Irish",
48
+ "wales": "Welsh", "african": "African", "malaysia": "Malaysian",
49
+ "bermuda": "Bermudian", "philippines": "Philippine",
50
+ "hongkong": "Hong Kong", "singapore": "Singaporean",
51
+ "southatlandtic": "Other"
52
+ }
53
+ def group_accents(raw_list):
54
+ return [(GROUP_MAP.get(r, r.capitalize()), p) for r, p in raw_list]
55
+
56
+ # ───────────────────────────────────────────────
57
+ # 4) Helper functions
58
+ # ───────────────────────────────────────────────
59
+ def download_extract_audio(url, out_vid="clip.mp4", out_wav="clip.wav",
60
+ max_duration=60, sr=16000):
61
+ if os.path.exists(out_vid): os.remove(out_vid)
62
+ try:
63
+ with YoutubeDL({"outtmpl": out_vid, "merge_output_format": "mp4"}) as ydl:
64
+ ydl.download([url])
65
+ except Exception as e:
66
+ raise RuntimeError(
67
+ "❌ Unable to access the video. This may be due to restricted or bot-protected content.\n"
68
+ "πŸ’‘ Tip: Use public video links like Loom or direct MP4 URLs instead."
69
+ ) from e
70
+
71
+ clip = VideoFileClip(out_vid)
72
+ used = min(clip.duration, max_duration)
73
+ sub = clip.subclip(0, used)
74
+ sub.audio.write_audiofile(out_wav, fps=sr, codec="pcm_s16le")
75
+ clip.close(); sub.close()
76
+ wav, rate = librosa.load(out_wav, sr=sr, mono=True)
77
+ return wav, rate, out_wav, out_vid
78
+
79
+ def detect_language_whisper(wav_path):
80
+ audio = whisper.load_audio(wav_path, sr=16000)
81
+ audio = whisper.pad_or_trim(audio)
82
+ mel = whisper.log_mel_spectrogram(audio).to(wmodel.device)
83
+ _, probs = wmodel.detect_language(mel)
84
+ lang = max(probs, key=probs.get)
85
+ conf = probs.get("en", 0.0) * 100
86
+ return lang, conf
87
+
88
+ def classify_clip_topk(wav_path, k=3):
89
+ out_prob, _, _, _ = classifier.classify_file(wav_path)
90
+ probs = out_prob.squeeze().cpu().numpy()
91
+ idxs = probs.argsort()[-k:][::-1]
92
+ return [(classifier.hparams.label_encoder.ind2lab[i], float(probs[i]))
93
+ for i in idxs]
94
+
95
+ # ───────────────────────────────────────────────
96
+ # 5) Streamlit UI
97
+ # ───────────────────────────────────────────────
98
+ st.title("🎀 English & Accent Detector")
99
+ st.write("""
100
+ This tool helps you determine if a speaker is speaking English and identifies their accent.
101
+
102
+ 🧭 **How to use:**
103
+ - Use **URL** for public video links (e.g., Loom, MP4 links).
104
+ - Avoid using YouTube links that require login, CAPTCHA, or age verification.
105
+ - Use **Upload** to submit local video files (MP4, MOV, WEBM, MKV).
106
+ - Use **Record** to record short audio snippets directly from your browser.
107
+ """)
108
+
109
+ st.sidebar.header("πŸ“₯ Input")
110
+ method = st.sidebar.radio("Input method", ["URL", "Upload", "Record"])
111
+
112
+ url = None
113
+ uploaded = None
114
+ audio_bytes = None
115
+
116
+ if method == "URL":
117
+ url = st.sidebar.text_input("Video URL (e.g. Loom, MP4)")
118
+ elif method == "Upload":
119
+ uploaded = st.sidebar.file_uploader("Upload a video file", type=["mp4", "mov", "webm", "mkv"])
120
+ elif method == "Record":
121
+ st.sidebar.write("πŸŽ™οΈ Click below to start recording (wait for microphone access prompt):")
122
+ audio_bytes = audio_recorder()
123
+ if not audio_bytes:
124
+ st.sidebar.info("Waiting for you to record your voice...")
125
+ else:
126
+ st.sidebar.success("Audio recorded successfully! You can now classify it.")
127
+
128
+ if st.sidebar.button("Classify Accent"):
129
+ with st.spinner("πŸ”Š Extracting audio..."):
130
+ try:
131
+ if method == "URL" and url:
132
+ wav, sr, wav_path, vid_path = download_extract_audio(url)
133
+ elif method == "Upload" and uploaded:
134
+ vid_path = tempfile.NamedTemporaryFile(
135
+ suffix=os.path.splitext(uploaded.name)[1], delete=False
136
+ ).name
137
+ with open(vid_path, "wb") as f:
138
+ f.write(uploaded.read())
139
+ clip = VideoFileClip(vid_path)
140
+ wav_path = "clip.wav"
141
+ clip.audio.write_audiofile(wav_path, fps=16000, codec="pcm_s16le")
142
+ clip.close()
143
+ wav, sr = librosa.load(wav_path, sr=16000, mono=True)
144
+ elif method == "Record" and audio_bytes:
145
+ wav_path = "recorded.wav"
146
+ with open(wav_path, "wb") as f:
147
+ f.write(audio_bytes)
148
+ wav, sr = librosa.load(wav_path, sr=16000, mono=True)
149
+ vid_path = None
150
+ else:
151
+ st.error("Please supply a valid input.")
152
+ st.stop()
153
+ except RuntimeError as e:
154
+ st.error(str(e))
155
+ st.stop()
156
+
157
+ left, right = st.columns([1, 2])
158
+ with left:
159
+ st.subheader("πŸ“Ί Preview")
160
+ if method == "Record":
161
+ st.audio(audio_bytes, format="audio/wav")
162
+ elif vid_path:
163
+ with open(vid_path, "rb") as f:
164
+ st.video(f.read())
165
+
166
+ with right:
167
+ with st.spinner("πŸ”Ž Detecting English..."):
168
+ lang_code, eng_conf = detect_language_whisper(wav_path)
169
+
170
+ if eng_conf >= 4.0:
171
+ st.markdown(
172
+ "<div style='background-color:#1b5e20; color:#a5d6a7; padding:8px;"
173
+ " border-radius:5px;'>βœ… <strong>English detected – classifying accent...</strong></div>",
174
+ unsafe_allow_html=True
175
+ )
176
+ with st.spinner("🎯 Classifying accent..."):
177
+ raw3 = classify_clip_topk(wav_path, k=3)
178
+ grouped = group_accents(raw3)
179
+
180
+ st.subheader("πŸ—£οΈ Accent Classification")
181
+ cols = st.columns(len(grouped))
182
+ for c, (lbl, p) in zip(cols, grouped):
183
+ c.markdown(
184
+ f"""<div style=\"border:1px solid #444; border-radius:8px; padding:15px; text-align:center;\">
185
+ <div style=\"font-size:1.1em; font-weight:bold; color:#90caf9\">{lbl}</div>
186
+ <div style=\"font-size:1.8em; color:#29b6f6;\">{p*100:5.1f}%</div>
187
+ </div>""",
188
+ unsafe_allow_html=True
189
+ )
190
+ else:
191
+ st.markdown(
192
+ "<div style='background-color:#b71c1c; color:#ffcdd2; padding:8px;"
193
+ " border-radius:5px;'>❌ <strong>English not detected</strong></div>",
194
+ unsafe_allow_html=True
195
+ )
196
+ name = tok.LANGUAGES.get(lang_code, lang_code).capitalize()
197
+ st.write(f"**Top detected language:** {name} ({eng_conf:.1f}% English)")
198
+
199
+ for p in (wav_path, vid_path):
200
+ if p and os.path.exists(p):
201
+ try:
202
+ os.remove(p)
203
+ except:
204
+ pass
src/streamlit_app.py DELETED
@@ -1,40 +0,0 @@
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
- ))