NickVerri commited on
Commit
d10fe14
·
verified ·
1 Parent(s): e24524c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -95
app.py CHANGED
@@ -1,25 +1,20 @@
1
  import streamlit as st
2
  import subprocess
3
- import whisper
4
  import json
5
  import os
6
  import requests
7
  import torch
8
- import torchaudio
 
9
  from datetime import timedelta
10
- from pyannote.audio import Pipeline
11
- from huggingface_hub import login, hf_hub_download
12
- from pydub import AudioSegment
13
 
14
  # --- Configuration & Tokens ---
15
- # Hardcode tokens here if you want to avoid UI input
16
  HARDCODED_HF_TOKEN = "PASTE_YOUR_HF_TOKEN_HERE"
17
  HARDCODED_GEMINI_KEY = ""
18
 
19
  ENV_HF_TOKEN = os.environ.get("HF_TOKEN", "")
20
  ENV_GEMINI_KEY = os.environ.get("GEMINI_API_KEY", "")
21
 
22
- # Determine active keys
23
  ACTIVE_HF_TOKEN = ENV_HF_TOKEN if ENV_HF_TOKEN else HARDCODED_HF_TOKEN
24
  ACTIVE_GEMINI_KEY = ENV_GEMINI_KEY if ENV_GEMINI_KEY else HARDCODED_GEMINI_KEY
25
 
@@ -87,13 +82,17 @@ def call_gemini_for_edl(transcript_data, story_prompt, api_key):
87
  return None
88
 
89
  # --- Streamlit UI ---
90
- st.set_page_config(page_title="DocAI Editor", layout="wide")
91
- st.title("Documentary AI: Pipeline (Stable 2.1)")
92
 
93
  with st.sidebar:
94
  st.header("Project Settings")
95
  fps = st.number_input("Timeline FPS", value=25)
96
 
 
 
 
 
97
  st.divider()
98
  st.info("API Keys are managed via Environment Secrets.")
99
  if not ACTIVE_GEMINI_KEY:
@@ -110,105 +109,83 @@ if uploaded_file:
110
  if not ACTIVE_HF_TOKEN or "PASTE_YOUR_HF_TOKEN" in ACTIVE_HF_TOKEN:
111
  st.error("Please provide a valid Hugging Face Token.")
112
  else:
113
- with st.spinner("Processing... This may take a moment."):
 
 
 
114
  # Save local temp file
115
  with open("temp_input", "wb") as f:
116
  f.write(uploaded_file.getbuffer())
117
 
118
- st.write("🎵 **Preprocessing Audio...**")
119
- try:
120
- # Use PyDub to convert to WAV (Mono, 16kHz)
121
- audio = AudioSegment.from_file("temp_input")
122
- audio = audio.set_channels(1)
123
- audio = audio.set_frame_rate(16000)
124
- audio.export("temp_audio.wav", format="wav")
125
- except Exception as e:
126
- st.error(f"Audio processing failed: {e}")
127
- st.stop()
128
 
129
- # 1. Diarization (Pyannote 2.1.1)
130
- st.write("🗣️ **Running Speaker Diarization...**")
131
- diarization = None
132
  try:
133
- # Log in globally
134
- login(token=ACTIVE_HF_TOKEN)
135
 
136
- # Use manual config download with explicit 'token' argument
137
- # This fixes the hf_hub_download error
138
- config_path = hf_hub_download(
139
- repo_id="pyannote/speaker-diarization",
140
- revision="2.1",
141
- filename="config.yaml",
142
- token=ACTIVE_HF_TOKEN
143
- )
144
 
145
- # Load pipeline from the manually downloaded config
146
- # We do NOT pass a token here because the config file is local
147
- pipeline = Pipeline.from_pretrained(config_path)
148
 
149
- if torch.cuda.is_available():
150
- st.write("🚀 Using GPU for Diarization")
151
- pipeline.to(torch.device("cuda"))
152
-
153
- # Run pipeline directly on file path
154
- diarization = pipeline("temp_audio.wav")
155
 
156
- except Exception as e:
157
- st.error(f"Diarization Error: {e}")
158
- diarization = None
159
-
160
- # 2. Whisper Transcription
161
- st.write("📝 **Transcribing with Whisper...**")
162
- device = "cuda" if torch.cuda.is_available() else "cpu"
163
- model = whisper.load_model("medium", device=device)
164
- result = model.transcribe("temp_audio.wav", word_timestamps=True)
165
-
166
- # 3. Alignment
167
- st.write("🔗 **Aligning Speakers...**")
168
- final_segments = []
169
- speaker_turns = []
170
-
171
- if diarization:
172
- try:
173
- # 2.1.1 returns a proper Annotation object directly
174
- for turn, _, speaker_id in diarization.itertracks(yield_label=True):
175
- speaker_turns.append({"start": turn.start, "end": turn.end, "speaker": speaker_id})
176
-
177
- if len(speaker_turns) > 0:
178
- st.write(f"✅ Found {len(speaker_turns)} speaker turns.")
179
- else:
180
- st.warning("⚠️ Pipeline ran but returned no tracks.")
181
- except AttributeError:
182
- st.error("Could not iterate tracks. Output object format mismatch.")
183
-
184
- for segment in result['segments']:
185
- mid_time = (segment['start'] + segment['end']) / 2
186
- speaker = "Unknown"
187
 
188
- if speaker_turns:
189
- for turn in speaker_turns:
190
- if turn["start"] <= mid_time <= turn["end"]:
191
- speaker = turn["speaker"]
192
- break
 
 
 
193
 
194
- if speaker == "Unknown":
195
- best_dist = 1.0
196
- for turn in speaker_turns:
197
- dist = min(abs(turn["start"] - mid_time), abs(turn["end"] - mid_time))
198
- if dist < best_dist:
199
- best_dist = dist
200
- speaker = turn["speaker"]
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- final_segments.append({
203
- "speaker": speaker,
204
- "text": segment['text'],
205
- "start": segment['start'],
206
- "end": segment['end'],
207
- "words": segment.get('words', [])
208
- })
209
-
210
- st.session_state.transcript = final_segments
211
- st.success("Complete!")
212
 
213
  if "transcript" in st.session_state:
214
  st.divider()
 
1
  import streamlit as st
2
  import subprocess
 
3
  import json
4
  import os
5
  import requests
6
  import torch
7
+ import whisperx
8
+ import gc
9
  from datetime import timedelta
 
 
 
10
 
11
  # --- Configuration & Tokens ---
 
12
  HARDCODED_HF_TOKEN = "PASTE_YOUR_HF_TOKEN_HERE"
13
  HARDCODED_GEMINI_KEY = ""
14
 
15
  ENV_HF_TOKEN = os.environ.get("HF_TOKEN", "")
16
  ENV_GEMINI_KEY = os.environ.get("GEMINI_API_KEY", "")
17
 
 
18
  ACTIVE_HF_TOKEN = ENV_HF_TOKEN if ENV_HF_TOKEN else HARDCODED_HF_TOKEN
19
  ACTIVE_GEMINI_KEY = ENV_GEMINI_KEY if ENV_GEMINI_KEY else HARDCODED_GEMINI_KEY
20
 
 
82
  return None
83
 
84
  # --- Streamlit UI ---
85
+ st.set_page_config(page_title="DocAI Editor (WhisperX)", layout="wide")
86
+ st.title("Documentary AI: Pipeline (WhisperX)")
87
 
88
  with st.sidebar:
89
  st.header("Project Settings")
90
  fps = st.number_input("Timeline FPS", value=25)
91
 
92
+ st.header("Model Settings")
93
+ model_size = st.selectbox("Whisper Model Size", ["large-v3", "large-v2", "medium", "base"], index=0)
94
+ num_speakers = st.number_input("Number of Speakers (Optional)", min_value=0, value=0, help="Set to 0 for auto-detect")
95
+
96
  st.divider()
97
  st.info("API Keys are managed via Environment Secrets.")
98
  if not ACTIVE_GEMINI_KEY:
 
109
  if not ACTIVE_HF_TOKEN or "PASTE_YOUR_HF_TOKEN" in ACTIVE_HF_TOKEN:
110
  st.error("Please provide a valid Hugging Face Token.")
111
  else:
112
+ status_container = st.empty()
113
+ with status_container.container():
114
+ st.write("🔄 **Processing Started...**")
115
+
116
  # Save local temp file
117
  with open("temp_input", "wb") as f:
118
  f.write(uploaded_file.getbuffer())
119
 
120
+ st.write("🎵 **Extracting Audio (WAV)...**")
121
+ # WhisperX prefers 16k mono wav
122
+ subprocess.run([
123
+ "ffmpeg", "-i", "temp_input",
124
+ "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1",
125
+ "temp_audio.wav", "-y"
126
+ ])
 
 
 
127
 
 
 
 
128
  try:
129
+ device = "cuda" if torch.cuda.is_available() else "cpu"
130
+ st.write(f"🚀 **Loading WhisperX on {device}...**")
131
 
132
+ # 1. Transcribe with WhisperX (Faster-Whisper)
133
+ batch_size = 16 # Reduce if low VRAM
134
+ # Use float16 for GPU, int8 for CPU
135
+ compute_type = "float16" if device == "cuda" else "int8"
 
 
 
 
136
 
137
+ model = whisperx.load_model(model_size, device, compute_type=compute_type)
 
 
138
 
139
+ st.write("📝 **Transcribing...**")
140
+ audio = whisperx.load_audio("temp_audio.wav")
141
+ result = model.transcribe(audio, batch_size=batch_size)
 
 
 
142
 
143
+ # Cleanup VRAM
144
+ model_a = None
145
+ gc.collect()
146
+ torch.cuda.empty_cache()
147
+
148
+ # 2. Align (Improves timestamp accuracy)
149
+ st.write("⏱️ **Aligning Audio...**")
150
+ model_a, metadata = whisperx.load_align_model(language_code=result["language"], device=device)
151
+ result = whisperx.align(result["segments"], model_a, metadata, audio, device, return_char_alignments=False)
152
+
153
+ # Cleanup VRAM
154
+ model_a = None
155
+ gc.collect()
156
+ torch.cuda.empty_cache()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ # 3. Diarize
159
+ st.write("🗣️ **Diarizing Speakers...**")
160
+ diarize_model = whisperx.DiarizationPipeline(use_auth_token=ACTIVE_HF_TOKEN, device=device)
161
+
162
+ # Optional: Enforce speaker count
163
+ diarize_kwargs = {}
164
+ if num_speakers > 0:
165
+ diarize_kwargs = {"min_speakers": num_speakers, "max_speakers": num_speakers}
166
 
167
+ diarize_segments = diarize_model(audio, **diarize_kwargs)
168
+
169
+ # 4. Assign Speakers to Words
170
+ st.write("🔗 **Merging Transcripts...**")
171
+ final_result = whisperx.assign_word_speakers(diarize_segments, result)
172
+
173
+ # Format for Gemini
174
+ processed_segments = []
175
+ for segment in final_result["segments"]:
176
+ processed_segments.append({
177
+ "speaker": segment.get("speaker", "Unknown"),
178
+ "text": segment["text"],
179
+ "start": segment["start"],
180
+ "end": segment["end"]
181
+ })
182
+
183
+ st.session_state.transcript = processed_segments
184
+ st.success(f"Complete! Found {len(processed_segments)} segments.")
185
 
186
+ except Exception as e:
187
+ st.error(f"Processing Error: {e}")
188
+ st.stop()
 
 
 
 
 
 
 
189
 
190
  if "transcript" in st.session_state:
191
  st.divider()