ceh-vedant commited on
Commit
1458172
·
verified ·
1 Parent(s): 45baed4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -55
app.py CHANGED
@@ -1,8 +1,8 @@
1
  import sys
2
  import os
3
  import types
4
- import subprocess
5
  import logging
 
6
 
7
  # Shim for removed audioop module (Python 3.13+)
8
  if 'audioop' not in sys.modules:
@@ -15,77 +15,88 @@ import matplotlib
15
  matplotlib.use('Agg')
16
 
17
  logger = logging.getLogger(__name__)
 
18
 
19
  # ---------------------------------------------------------------------------
20
- # Monkey-patch TRIBE's whisperx subprocess call to use whisperx as a Python
21
- # library instead. TRIBE internally calls `uvx whisperx ...` via subprocess,
22
- # which fails in HuggingFace Spaces. This patch replaces that with a direct
23
- # Python call to the whisperx library.
 
 
24
  # ---------------------------------------------------------------------------
25
  def _patched_get_transcript_from_audio(wav_filename, language="english"):
26
- """Replacement for ExtractWordsFromAudio._get_transcript_from_audio
27
- that uses whisperx as a Python library instead of a subprocess."""
28
- import whisperx
29
- import torch
30
- from pathlib import Path
31
-
32
- language_codes = dict(
33
- english="en", french="fr", spanish="es", dutch="nl", chinese="zh"
34
- )
35
- if language not in language_codes:
36
- raise ValueError(f"Language {language} not supported")
37
-
38
- device = "cuda" if torch.cuda.is_available() else "cpu"
39
- compute_type = "float16" if device == "cuda" else "int8"
40
- lang_code = language_codes[language]
41
 
42
- logger.info("Loading whisperx model (patched)...")
43
- wx_model = whisperx.load_model("base", device, compute_type=compute_type, language=lang_code)
44
-
45
- logger.info(f"Transcribing {wav_filename}...")
46
- audio = whisperx.load_audio(str(wav_filename))
47
- result = wx_model.transcribe(audio, batch_size=4)
 
48
 
49
- logger.info("Aligning words...")
50
- model_a, metadata = whisperx.load_align_model(
51
- language_code=lang_code, device=device
52
- )
53
- result = whisperx.align(
54
- result["segments"], model_a, metadata, audio, device,
55
- return_char_alignments=False
56
- )
57
 
58
- import pandas as pd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  words = []
60
- for i, segment in enumerate(result.get("segments", [])):
61
- sentence = segment.get("text", "").replace('"', "")
62
- for word in segment.get("words", []):
63
- if "start" not in word:
64
- continue
 
65
  words.append({
66
- "text": word["word"].replace('"', ""),
67
- "start": word["start"],
68
- "duration": word["end"] - word["start"],
69
- "sequence_id": i,
70
- "sentence": sentence,
71
  })
 
72
 
73
  return pd.DataFrame(words)
74
 
75
 
76
- def apply_whisperx_patch():
77
- """Apply the monkey-patch to TRIBE's ExtractWordsFromAudio class."""
 
 
 
 
78
  try:
79
  from tribev2.eventstransforms import ExtractWordsFromAudio
80
  ExtractWordsFromAudio._get_transcript_from_audio = staticmethod(
81
  _patched_get_transcript_from_audio
82
  )
83
- logger.info("Successfully patched ExtractWordsFromAudio to use whisperx Python library")
84
  except Exception as e:
85
  logger.warning(f"Could not patch ExtractWordsFromAudio: {e}")
86
 
87
- # Apply the patch before loading the model
88
- apply_whisperx_patch()
89
 
90
  # ---------------------------------------------------------------------------
91
  # Model loading
@@ -97,8 +108,7 @@ def load_model():
97
  if model is not None:
98
  return "✅ Already loaded!"
99
  try:
100
- # Re-apply patch in case import order matters
101
- apply_whisperx_patch()
102
  from tribev2 import TribeModel
103
  model = TribeModel.from_pretrained("facebook/tribev2", cache_folder="/tmp/tribe_cache")
104
  return "✅ Model loaded!"
@@ -197,6 +207,8 @@ def generate_suggestions(scores, overall):
197
  # Main analysis function
198
  # ---------------------------------------------------------------------------
199
  def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
 
 
200
  if input_mode == "Text" and (not script_text or not script_text.strip()):
201
  return None, None, "⚠️ Please paste your script text first.", None
202
  if input_mode == "Audio" and audio_file is None:
@@ -212,9 +224,6 @@ def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
212
  if input_mode == "Text":
213
  progress(0.2, desc="Converting text to speech...")
214
 
215
- # Convert text → audio with gTTS, then feed as audio_path.
216
- # This avoids TRIBE's internal TextToEvents path which also
217
- # calls whisperx via subprocess after doing the same gTTS step.
218
  from gtts import gTTS
219
  from langdetect import detect
220
 
@@ -224,6 +233,10 @@ def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
224
  tts = gTTS(text=text, lang=lang)
225
  tts.save(audio_path)
226
 
 
 
 
 
227
  progress(0.4, desc="Running TRIBE v2 on generated audio...")
228
  df = model.get_events_dataframe(audio_path=audio_path)
229
 
@@ -234,6 +247,9 @@ def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
234
  audio_path = f"/tmp/input_audio{ext}"
235
  shutil.copy(audio_file, audio_path)
236
 
 
 
 
237
  progress(0.4, desc="Running TRIBE v2 on audio...")
238
  df = model.get_events_dataframe(audio_path=audio_path)
239
 
@@ -257,6 +273,8 @@ def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
257
  full_error = traceback.format_exc()
258
  print(full_error)
259
  return None, None, f"❌ Error:\n{str(e)}\n\nFull traceback:\n{full_error}", None
 
 
260
 
261
  # ---------------------------------------------------------------------------
262
  # Gradio UI
 
1
  import sys
2
  import os
3
  import types
 
4
  import logging
5
+ import re
6
 
7
  # Shim for removed audioop module (Python 3.13+)
8
  if 'audioop' not in sys.modules:
 
15
  matplotlib.use('Agg')
16
 
17
  logger = logging.getLogger(__name__)
18
+ logging.basicConfig(level=logging.INFO)
19
 
20
  # ---------------------------------------------------------------------------
21
+ # Monkey-patch TRIBE's ExtractWordsFromAudio to build word-level events
22
+ # WITHOUT calling whisperx (which requires CUDA libs unavailable on CPU).
23
+ #
24
+ # Instead, we use a simple heuristic: split the transcript text into words
25
+ # and distribute them evenly across the audio duration. This gives TRIBE
26
+ # enough word-level signal for its text encoder without needing ASR.
27
  # ---------------------------------------------------------------------------
28
  def _patched_get_transcript_from_audio(wav_filename, language="english"):
29
+ """CPU-safe replacement that creates word events from audio duration.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ When the audio was generated from known text (gTTS), the global
32
+ CURRENT_SCRIPT_TEXT will contain that text. Otherwise we create
33
+ a minimal placeholder so TRIBE's pipeline doesn't crash.
34
+ """
35
+ import pandas as pd
36
+ import soundfile as sf
37
+ from pathlib import Path
38
 
39
+ wav_filename = Path(wav_filename)
 
 
 
 
 
 
 
40
 
41
+ # Get audio duration
42
+ try:
43
+ info = sf.info(str(wav_filename))
44
+ duration = info.duration
45
+ except Exception:
46
+ duration = 30.0 # fallback
47
+
48
+ # Use the known script text if available, otherwise a placeholder
49
+ text = _CURRENT_SCRIPT_TEXT or "audio content placeholder"
50
+
51
+ # Tokenize into words
52
+ raw_words = text.split()
53
+ if not raw_words:
54
+ return pd.DataFrame(columns=["text", "start", "duration", "sequence_id", "sentence"])
55
+
56
+ # Split into sentences (rough: split on . ! ?)
57
+ sentences = re.split(r'(?<=[.!?])\s+', text)
58
+ sentences = [s.strip() for s in sentences if s.strip()]
59
+ if not sentences:
60
+ sentences = [text]
61
+
62
+ # Distribute words evenly across the audio duration
63
+ word_duration = duration / len(raw_words)
64
  words = []
65
+ word_idx = 0
66
+ for sent_idx, sentence in enumerate(sentences):
67
+ sent_words = sentence.split()
68
+ for w in sent_words:
69
+ if word_idx >= len(raw_words):
70
+ break
71
  words.append({
72
+ "text": w.replace('"', ''),
73
+ "start": word_idx * word_duration,
74
+ "duration": word_duration * 0.9,
75
+ "sequence_id": sent_idx,
76
+ "sentence": sentence.replace('"', ''),
77
  })
78
+ word_idx += 1
79
 
80
  return pd.DataFrame(words)
81
 
82
 
83
+ # Global to pass text from the analyze function to the monkey-patch
84
+ _CURRENT_SCRIPT_TEXT = None
85
+
86
+
87
+ def apply_patches():
88
+ """Patch TRIBE's ExtractWordsFromAudio to avoid whisperx/CUDA dependency."""
89
  try:
90
  from tribev2.eventstransforms import ExtractWordsFromAudio
91
  ExtractWordsFromAudio._get_transcript_from_audio = staticmethod(
92
  _patched_get_transcript_from_audio
93
  )
94
+ logger.info("Patched ExtractWordsFromAudio (CPU-safe, no whisperx)")
95
  except Exception as e:
96
  logger.warning(f"Could not patch ExtractWordsFromAudio: {e}")
97
 
98
+ # Apply patches at import time
99
+ apply_patches()
100
 
101
  # ---------------------------------------------------------------------------
102
  # Model loading
 
108
  if model is not None:
109
  return "✅ Already loaded!"
110
  try:
111
+ apply_patches() # re-apply in case import order matters
 
112
  from tribev2 import TribeModel
113
  model = TribeModel.from_pretrained("facebook/tribev2", cache_folder="/tmp/tribe_cache")
114
  return "✅ Model loaded!"
 
207
  # Main analysis function
208
  # ---------------------------------------------------------------------------
209
  def analyze(input_mode, script_text, audio_file, progress=gr.Progress()):
210
+ global _CURRENT_SCRIPT_TEXT
211
+
212
  if input_mode == "Text" and (not script_text or not script_text.strip()):
213
  return None, None, "⚠️ Please paste your script text first.", None
214
  if input_mode == "Audio" and audio_file is None:
 
224
  if input_mode == "Text":
225
  progress(0.2, desc="Converting text to speech...")
226
 
 
 
 
227
  from gtts import gTTS
228
  from langdetect import detect
229
 
 
233
  tts = gTTS(text=text, lang=lang)
234
  tts.save(audio_path)
235
 
236
+ # Store text so the monkey-patched transcriber can use it
237
+ # instead of running ASR on the audio we just synthesised.
238
+ _CURRENT_SCRIPT_TEXT = text
239
+
240
  progress(0.4, desc="Running TRIBE v2 on generated audio...")
241
  df = model.get_events_dataframe(audio_path=audio_path)
242
 
 
247
  audio_path = f"/tmp/input_audio{ext}"
248
  shutil.copy(audio_file, audio_path)
249
 
250
+ # No known text for uploaded audio
251
+ _CURRENT_SCRIPT_TEXT = None
252
+
253
  progress(0.4, desc="Running TRIBE v2 on audio...")
254
  df = model.get_events_dataframe(audio_path=audio_path)
255
 
 
273
  full_error = traceback.format_exc()
274
  print(full_error)
275
  return None, None, f"❌ Error:\n{str(e)}\n\nFull traceback:\n{full_error}", None
276
+ finally:
277
+ _CURRENT_SCRIPT_TEXT = None
278
 
279
  # ---------------------------------------------------------------------------
280
  # Gradio UI