CleanSong commited on
Commit
73c22fe
Β·
verified Β·
1 Parent(s): eff180b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -75
app.py CHANGED
@@ -1,9 +1,11 @@
 
 
 
 
1
  import gradio as gr
2
  import torch
3
  import torchaudio
4
- import os
5
  import requests
6
- import tempfile
7
  from faster_whisper import WhisperModel
8
 
9
  # ================================
@@ -15,7 +17,8 @@ FAST_MODEL_NAME = os.getenv("FAST_WHISPER_MODEL", "base")
15
  COMPUTE_TYPE = "float16" if torch.cuda.is_available() else "int8"
16
 
17
  BAD_WORD_URL = (
18
- "https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en"
 
19
  )
20
 
21
  # ================================
@@ -32,29 +35,17 @@ def get_bad_words():
32
  for w in line.split()
33
  if w.strip()
34
  }
35
- words.add("hell")
36
- words.add("dam")
37
- words.add("damn")
38
- words.add("yeah")
39
  print(f"βœ… Loaded {len(words)} bad words.")
40
  return words
41
  except Exception as e:
42
  print(f"⚠️ Failed to fetch list: {e}")
43
 
44
- return {"hell"} # fallback
45
-
46
- BAD_WORDS = get_bad_words()
47
 
48
- # ================================
49
- # LOAD MODELS
50
- # ================================
51
- print(f"πŸš€ Loading FAST Whisper: {FAST_MODEL_NAME} ({COMPUTE_TYPE}) on {DEVICE}")
52
- fast_model = WhisperModel(FAST_MODEL_NAME, device=DEVICE, compute_type=COMPUTE_TYPE)
53
 
54
- print(f"πŸš€ Loading LARGE Whisper: {MODEL_NAME} ({COMPUTE_TYPE}) on {DEVICE}")
55
- large_model = WhisperModel(MODEL_NAME, device=DEVICE, compute_type=COMPUTE_TYPE)
56
-
57
- print("βœ… All models ready!\n")
58
 
59
 
60
  # ================================
@@ -62,24 +53,32 @@ print("βœ… All models ready!\n")
62
  # ================================
63
  def load_audio_safe(path, target_sr=16000):
64
  wav, sr = torchaudio.load(path)
65
-
66
  if wav.shape[0] > 1:
67
  wav = wav.mean(dim=0, keepdim=True)
68
-
69
  if sr != target_sr:
70
  wav = torchaudio.functional.resample(wav, sr, target_sr)
71
-
72
  return wav, target_sr
73
 
74
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  # ================================
76
  # MAIN TRANSCRIBE FUNCTION
77
  # ================================
78
  def transcribe(file_path):
79
 
80
- # --- LOAD + NORMALIZE AUDIO ---
81
  wav, sr = load_audio_safe(file_path)
82
-
83
  fixed_path = "input_fixed.wav"
84
  torchaudio.save(fixed_path, wav, sr)
85
 
@@ -90,39 +89,31 @@ def transcribe(file_path):
90
  fixed_path,
91
  beam_size=1,
92
  word_timestamps=True,
93
- vad_filter=True
94
  )
95
 
96
  transcript = []
97
  sample_rate = getattr(fast_info, "sample_rate", sr)
98
 
99
  for seg in fast_segments:
100
- if getattr(seg, "words", None):
101
- for w in seg.words:
102
- word = {
103
- re.sub(r"[^\w]", "", w.lower())
104
- for line in r.text.splitlines()
105
- for w in line.split()
106
- if w.strip()
107
- }
108
- is_explicit = word.lower() in BAD_WORDS
109
-
110
- transcript.append({
111
- "word": word,
112
- "start": float(w.start),
113
- "end": float(w.end),
114
- "explicit": is_explicit,
115
- "explicit_fast": is_explicit
116
- })
117
- else:
118
- # skip empty text blocks β€” they break everything
119
  continue
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  # =====================================
122
  # EARLY EXIT IF NO EXPLICIT WORDS
123
  # =====================================
124
  flagged = [w for w in transcript if w["explicit_fast"]]
125
-
126
  if not flagged:
127
  print("βœ… No explicit words detected β€” returning fast transcript.")
128
  return transcript
@@ -131,74 +122,69 @@ def transcribe(file_path):
131
  # 2) REFINE PASS β€” only explicit words
132
  # =====================================
133
  final = []
134
- refine_index = 0
135
 
136
  for entry in transcript:
137
-
138
- # If this entry is NOT explicit, keep it untouched
139
  if not entry["explicit_fast"]:
140
  final.append(entry)
141
  continue
142
 
143
- # --- Extract audio chunk for just this word ---
144
  start_s = entry["start"]
145
  end_s = entry["end"]
146
-
147
  start_sample = int(start_s * sample_rate)
148
  end_sample = int(end_s * sample_rate)
149
  chunk = wav[:, start_sample:end_sample]
150
 
151
- # Safety check β€” some timestamps can collapse
152
  if chunk.numel() == 0:
153
  final.append(entry)
154
  continue
155
 
156
- # Save chunk
157
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
158
  chunk_path = tmp.name
159
  torchaudio.save(chunk_path, chunk, sample_rate)
160
 
161
- # --- RUN LARGE MODEL ON CHUNK ---
162
  try:
163
  refined_segs, _ = large_model.transcribe(
164
  chunk_path,
165
  beam_size=5,
166
  word_timestamps=True,
167
- vad_filter=False
168
  )
169
- except Exception:
170
- # If large model fails, keep original
171
  final.append(entry)
 
172
  continue
173
 
174
  os.remove(chunk_path)
175
 
176
- # Extract refined words
177
  refined_words = []
178
  for seg in refined_segs:
179
- if getattr(seg, "words", None):
180
- for w in seg.words:
181
- refined_words.append({
182
- "word": w.word.strip(),
183
- "start": float(w.start) + start_s,
184
- "end": float(w.end) + start_s,
185
- "explicit": entry["explicit_fast"],
186
- "explicit_fast": entry["explicit_fast"]
187
- })
188
-
189
- # If nothing was returned, fallback
 
190
  if not refined_words:
191
  final.append(entry)
192
  continue
193
 
194
- # Append refined entries
195
  final.extend(refined_words)
196
 
197
- # ================================
198
- # SORT BY TIMESTAMP (CRITICAL)
199
- # ================================
200
  final.sort(key=lambda x: x["start"])
201
-
202
  return final
203
 
204
 
@@ -209,8 +195,12 @@ iface = gr.Interface(
209
  fn=transcribe,
210
  inputs=gr.Audio(type="filepath", label="Upload Vocals"),
211
  outputs=gr.JSON(label="Transcript with Explicit Flags"),
212
- title="CleanSong AI β€” Whisper Transcriber (Fixed, Stable, Hybrid)",
213
- description="Fast model detects explicit words β†’ Large model refines only those segments."
 
 
 
 
214
  )
215
 
216
  if __name__ == "__main__":
 
1
+ import re
2
+ import os
3
+ import tempfile
4
+
5
  import gradio as gr
6
  import torch
7
  import torchaudio
 
8
  import requests
 
9
  from faster_whisper import WhisperModel
10
 
11
  # ================================
 
17
  COMPUTE_TYPE = "float16" if torch.cuda.is_available() else "int8"
18
 
19
  BAD_WORD_URL = (
20
+ "https://raw.githubusercontent.com/LDNOOBW/"
21
+ "List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en"
22
  )
23
 
24
  # ================================
 
35
  for w in line.split()
36
  if w.strip()
37
  }
38
+ # Extra words to always catch
39
+ words.update({"hell", "dam", "damn", "yeah"})
 
 
40
  print(f"βœ… Loaded {len(words)} bad words.")
41
  return words
42
  except Exception as e:
43
  print(f"⚠️ Failed to fetch list: {e}")
44
 
45
+ return {"fuck", "shit", "bitch", "ass", "damn", "hell"} # fallback
 
 
46
 
 
 
 
 
 
47
 
48
+ BAD_WORDS = get_bad_words()
 
 
 
49
 
50
 
51
  # ================================
 
53
  # ================================
54
  def load_audio_safe(path, target_sr=16000):
55
  wav, sr = torchaudio.load(path)
 
56
  if wav.shape[0] > 1:
57
  wav = wav.mean(dim=0, keepdim=True)
 
58
  if sr != target_sr:
59
  wav = torchaudio.functional.resample(wav, sr, target_sr)
 
60
  return wav, target_sr
61
 
62
 
63
+ # ================================
64
+ # LOAD MODELS
65
+ # ================================
66
+ print(f"πŸš€ Loading FAST Whisper: {FAST_MODEL_NAME} ({COMPUTE_TYPE}) on {DEVICE}")
67
+ fast_model = WhisperModel(FAST_MODEL_NAME, device=DEVICE, compute_type=COMPUTE_TYPE)
68
+
69
+ print(f"πŸš€ Loading LARGE Whisper: {MODEL_NAME} ({COMPUTE_TYPE}) on {DEVICE}")
70
+ large_model = WhisperModel(MODEL_NAME, device=DEVICE, compute_type=COMPUTE_TYPE)
71
+
72
+ print("βœ… All models ready!\n")
73
+
74
+
75
  # ================================
76
  # MAIN TRANSCRIBE FUNCTION
77
  # ================================
78
  def transcribe(file_path):
79
 
80
+ # Load + normalize audio
81
  wav, sr = load_audio_safe(file_path)
 
82
  fixed_path = "input_fixed.wav"
83
  torchaudio.save(fixed_path, wav, sr)
84
 
 
89
  fixed_path,
90
  beam_size=1,
91
  word_timestamps=True,
92
+ vad_filter=True,
93
  )
94
 
95
  transcript = []
96
  sample_rate = getattr(fast_info, "sample_rate", sr)
97
 
98
  for seg in fast_segments:
99
+ if not getattr(seg, "words", None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  continue
101
+ for w in seg.words:
102
+ # FIX: was incorrectly re-running the bad word set comprehension here
103
+ clean_word = re.sub(r"[^\w]", "", w.word.strip().lower())
104
+ is_explicit = clean_word in BAD_WORDS
105
+ transcript.append({
106
+ "word": w.word.strip(),
107
+ "start": float(w.start),
108
+ "end": float(w.end),
109
+ "explicit": is_explicit,
110
+ "explicit_fast": is_explicit,
111
+ })
112
 
113
  # =====================================
114
  # EARLY EXIT IF NO EXPLICIT WORDS
115
  # =====================================
116
  flagged = [w for w in transcript if w["explicit_fast"]]
 
117
  if not flagged:
118
  print("βœ… No explicit words detected β€” returning fast transcript.")
119
  return transcript
 
122
  # 2) REFINE PASS β€” only explicit words
123
  # =====================================
124
  final = []
 
125
 
126
  for entry in transcript:
127
+ # Not explicit β€” keep untouched
 
128
  if not entry["explicit_fast"]:
129
  final.append(entry)
130
  continue
131
 
132
+ # Extract audio chunk for just this word
133
  start_s = entry["start"]
134
  end_s = entry["end"]
 
135
  start_sample = int(start_s * sample_rate)
136
  end_sample = int(end_s * sample_rate)
137
  chunk = wav[:, start_sample:end_sample]
138
 
139
+ # Safety: collapsed timestamp
140
  if chunk.numel() == 0:
141
  final.append(entry)
142
  continue
143
 
144
+ # Save chunk to temp file
145
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
146
  chunk_path = tmp.name
147
  torchaudio.save(chunk_path, chunk, sample_rate)
148
 
149
+ # Run large model on chunk
150
  try:
151
  refined_segs, _ = large_model.transcribe(
152
  chunk_path,
153
  beam_size=5,
154
  word_timestamps=True,
155
+ vad_filter=False,
156
  )
157
+ except Exception as e:
158
+ print(f"⚠️ Large model failed on chunk: {e} β€” keeping fast result")
159
  final.append(entry)
160
+ os.remove(chunk_path)
161
  continue
162
 
163
  os.remove(chunk_path)
164
 
165
+ # Extract refined words, offset timestamps back to full-track time
166
  refined_words = []
167
  for seg in refined_segs:
168
+ if not getattr(seg, "words", None):
169
+ continue
170
+ for w in seg.words:
171
+ refined_words.append({
172
+ "word": w.word.strip(),
173
+ "start": float(w.start) + start_s,
174
+ "end": float(w.end) + start_s,
175
+ "explicit": entry["explicit_fast"],
176
+ "explicit_fast": entry["explicit_fast"],
177
+ })
178
+
179
+ # Fallback if large model returned nothing
180
  if not refined_words:
181
  final.append(entry)
182
  continue
183
 
 
184
  final.extend(refined_words)
185
 
186
+ # Sort by timestamp (critical for assembler)
 
 
187
  final.sort(key=lambda x: x["start"])
 
188
  return final
189
 
190
 
 
195
  fn=transcribe,
196
  inputs=gr.Audio(type="filepath", label="Upload Vocals"),
197
  outputs=gr.JSON(label="Transcript with Explicit Flags"),
198
+ title="CleanSong AI β€” Whisper Transcriber",
199
+ description=(
200
+ "Fast model detects explicit words β†’ "
201
+ "Large model refines only those segments. "
202
+ "Returns word-level timestamps."
203
+ ),
204
  )
205
 
206
  if __name__ == "__main__":