Turbiling commited on
Commit
99e68b7
·
verified ·
1 Parent(s): dadd64d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -239
app.py CHANGED
@@ -1,290 +1,150 @@
1
- # app.py
2
  import os
3
  import gradio as gr
4
  import tempfile
5
- import shutil
6
- import glob
7
- import subprocess
8
- import requests
9
  import yt_dlp
10
- from pathlib import Path
11
  from pydub import AudioSegment
12
  from groq import Groq
13
 
14
- # -----------------------
15
- # Environment variables
16
- # -----------------------
17
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
18
- HUGGINGFACE_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
 
19
 
20
- if not GROQ_API_KEY or not HUGGINGFACE_API_TOKEN:
21
- raise EnvironmentError("Please set GROQ_API_KEY and HUGGINGFACE_API_TOKEN in Space settings.")
22
-
23
- # Initialize Groq client
24
  groq_client = Groq(api_key=GROQ_API_KEY)
25
 
26
- # -----------------------
27
- # Utilities
28
- # -----------------------
29
-
30
  def download_youtube_audio(youtube_url):
31
- """
32
- Download audio into a temporary directory and return the downloaded filepath.
33
- """
34
- tmpdir = tempfile.mkdtemp(prefix="yt_")
35
- outtmpl = os.path.join(tmpdir, "%(id)s.%(ext)s")
36
- ydl_opts = {
37
- "format": "bestaudio/best",
38
- "outtmpl": outtmpl,
39
- "quiet": True,
40
- "no_warnings": True,
41
- "postprocessors": [{
42
- "key": "FFmpegExtractAudio",
43
- "preferredcodec": "mp3",
44
- "preferredquality": "192",
45
- }],
46
- }
47
- try:
48
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
49
- info = ydl.extract_info(youtube_url, download=True)
50
- # find the downloaded file
51
- files = glob.glob(os.path.join(tmpdir, "*"))
52
- if not files:
53
- raise FileNotFoundError("Downloaded file not found.")
54
- # pick the first audio file (mp3)
55
- audio_path = None
56
- for f in files:
57
- if f.lower().endswith((".mp3", ".m4a", ".wav", ".webm", ".aac", ".ogg")):
58
- audio_path = f
59
- break
60
- if audio_path is None:
61
- audio_path = files[0]
62
- return audio_path, tmpdir
63
- except Exception as e:
64
- # cleanup on failure
65
- try:
66
- shutil.rmtree(tmpdir)
67
- except:
68
- pass
69
- return f"❌ Error downloading audio: {e}", None
70
 
 
71
  def convert_to_wav(input_path):
72
- """
73
- Convert any audio/video to 16kHz mono WAV using ffmpeg.
74
- Always creates a new temp wav file (never overwrites input).
75
- Returns path to wav file.
76
- """
77
- import tempfile
78
- from pydub import AudioSegment
79
-
80
  try:
81
  tmp_wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False, prefix="conv_")
82
- tmp_wav.close()
83
  out_wav = tmp_wav.name
84
-
85
- # Use ffmpeg CLI
86
  result = subprocess.run(
87
  ["ffmpeg", "-y", "-i", str(input_path), "-ar", "16000", "-ac", "1", out_wav],
88
  stdout=subprocess.PIPE,
89
  stderr=subprocess.PIPE
90
  )
91
-
92
- if result.returncode != 0 or not os.path.exists(out_wav):
93
- print("⚠️ ffmpeg failed, trying pydub fallback...")
94
- try:
95
- audio = AudioSegment.from_file(input_path)
96
- audio = audio.set_frame_rate(16000).set_channels(1)
97
- audio.export(out_wav, format="wav")
98
- except Exception as e:
99
- raise RuntimeError(f"Both ffmpeg and pydub conversion failed: {e}")
100
-
101
  return out_wav
102
-
103
  except Exception as e:
104
- raise RuntimeError(f"Conversion error: {e}")
105
 
106
- def split_audio_to_chunks(wav_path, max_ms=5*60*1000):
107
- """
108
- Split WAV into chunks of max_ms milliseconds (default 5 minutes).
109
- Returns list of chunk file paths.
110
- """
111
- audio = AudioSegment.from_file(wav_path)
112
  chunks = []
113
- for i in range(0, len(audio), max_ms):
114
- chunk = audio[i:i+max_ms]
115
- tmpf = tempfile.NamedTemporaryFile(suffix=".wav", delete=False, prefix="chunk_")
116
- tmpf.close()
117
- chunk.export(tmpf.name, format="wav")
118
- chunks.append(tmpf.name)
119
  return chunks
120
 
121
- def transcribe_with_groq_chunks(audio_path):
122
- """
123
- Convert to wav, split into chunks, send each to Groq Whisper and combine results.
124
- Returns aggregated transcript string or error message string starting with ❌
125
- """
126
- try:
127
- wav = convert_to_wav(audio_path)
128
- except Exception as e:
129
- return f"❌ Error converting to WAV: {e}"
130
-
131
  try:
132
- chunks = split_audio_to_chunks(wav)
133
- except Exception as e:
134
- return f"❌ Error splitting audio: {e}"
135
-
136
- transcript_pieces = []
137
- try:
138
- for idx, chunk_path in enumerate(chunks, start=1):
139
- with open(chunk_path, "rb") as f:
140
- # call Groq transcription API
141
- resp = groq_client.audio.transcriptions.create(
142
  model="whisper-large-v3",
143
  file=f
144
  )
145
- # resp may be str, object with .text, or dict
146
- text_piece = ""
147
- if isinstance(resp, str):
148
- text_piece = resp
149
- elif hasattr(resp, "text"):
150
- text_piece = getattr(resp, "text")
151
- elif isinstance(resp, dict):
152
- text_piece = resp.get("text") or resp.get("transcription") or ""
153
- else:
154
- text_piece = str(resp)
155
- transcript_pieces.append(text_piece.strip())
156
  except Exception as e:
157
  return f"❌ Error during transcription: {e}"
158
- finally:
159
- # cleanup chunk files and wav
160
- for c in chunks:
161
- try:
162
- os.remove(c)
163
- except:
164
- pass
165
- try:
166
- if os.path.exists(wav):
167
- os.remove(wav)
168
- except:
169
- pass
170
-
171
- aggregated = "\n".join([p for p in transcript_pieces if p])
172
- return aggregated if aggregated else "❌ Empty transcription result."
173
 
174
- def summarize_via_hf(text, model, hf_token, params=None):
175
- """
176
- Call Hugging Face Inference HTTP API for summarization.
177
- model: model repo id (e.g., 'facebook/bart-large-cnn')
178
- params: optional dict for 'parameters' in request
179
- """
180
- url = f"https://api-inference.huggingface.co/models/{model}"
181
- headers = {"Authorization": f"Bearer {hf_token}"}
182
- payload = {"inputs": text}
183
- if params:
184
- payload["parameters"] = params
185
  try:
186
- r = requests.post(url, headers=headers, json=payload, timeout=120)
187
- except Exception as e:
188
- return f"❌ HTTP error contacting Hugging Face: {e}"
189
- if r.status_code != 200:
190
- # try to surface error message
191
- try:
192
- info = r.json()
193
- return f" Summarization failed: {info}"
194
- except:
195
- return f"❌ Summarization failed: HTTP {r.status_code}"
196
- try:
197
- out = r.json()
198
- # typical responses:
199
- # - [{'summary_text': '...'}] (BART)
200
- # - [{'generated_text': '...'}] (some models)
201
- if isinstance(out, list) and len(out) > 0:
202
- first = out[0]
203
- if isinstance(first, dict) and "summary_text" in first:
204
- return first["summary_text"]
205
- if isinstance(first, dict) and "generated_text" in first:
206
- return first["generated_text"]
207
- if isinstance(first, str):
208
- return first
209
- # fallback: stringify first element
210
- return str(first)
211
- elif isinstance(out, dict) and "summary_text" in out:
212
- return out["summary_text"]
213
- elif isinstance(out, str):
214
- return out
215
- else:
216
- return str(out)
217
  except Exception as e:
218
- return f"❌ Error parsing summarization response: {e}"
219
-
220
- # -----------------------
221
- # Main pipeline
222
- # -----------------------
223
 
224
- def process_input(youtube_url, uploaded_audio, summary_lang):
225
- """
226
- Main handler for Gradio:
227
- - Accepts optional youtube_url or uploaded_audio (filepath)
228
- - summary_lang: 'English' or 'Urdu'
229
- Returns: status, transcript, summary
230
- """
231
- tempdir_to_cleanup = None
232
  try:
233
- if youtube_url and youtube_url.strip():
234
- audio_path_or_err, tmpdir = download_youtube_audio(youtube_url.strip())
235
- if isinstance(audio_path_or_err, str) and audio_path_or_err.startswith("❌"):
236
- return audio_path_or_err, "", ""
237
- audio_path = audio_path_or_err
238
- tempdir_to_cleanup = tmpdir
239
- elif uploaded_audio:
240
- audio_path = uploaded_audio
241
  else:
242
- return "❌ Please upload an audio file or paste a YouTube link.", "", ""
243
 
244
- # Transcribe (with chunking)
245
- transcript = transcribe_with_groq_chunks(audio_path)
246
  if transcript.startswith("❌"):
247
  return transcript, "", ""
248
 
249
- # Summarize using appropriate model via HF HTTP
250
- if summary_lang == "English":
251
- model = "facebook/bart-large-cnn"
252
- params = {"min_length": 30, "max_length": 250}
253
- summary = summarize_via_hf(transcript, model, HUGGINGFACE_API_TOKEN, params=params)
254
- else: # Urdu
255
- # Use mBART: we ask the model to produce an Urdu summary.
256
- model = "facebook/mbart-large-50-many-to-many-mmt"
257
- # Provide a short Urdu instruction + the text (in case transcript is English; model will try to summarize in Urdu)
258
- prompt_text = f"مندرجہ ذیل عبارت کا جامع اردو خلاصہ لکھیں:\n\n{transcript}"
259
- params = {"min_length": 30, "max_length": 250}
260
- summary = summarize_via_hf(prompt_text, model, HUGGINGFACE_API_TOKEN, params=params)
261
-
262
- return "✅ Transcription & Summarization completed.", transcript, summary
263
 
264
- finally:
265
- # cleanup downloaded tempdir (if any)
266
- if tempdir_to_cleanup:
267
- try:
268
- shutil.rmtree(tempdir_to_cleanup)
269
- except:
270
- pass
271
 
272
- # -----------------------
273
- # Gradio UI
274
- # -----------------------
275
- with gr.Blocks(title="SmartTranscribe — YouTube + Upload (Urdu/English)") as demo:
276
- gr.Markdown("## SmartTranscribe — Upload audio or paste YouTube link. Select summary language (English/Urdu).")
277
  with gr.Row():
278
- youtube_input = gr.Textbox(label="YouTube Link (optional)", placeholder="https://www.youtube.com/watch?v=...")
279
- summary_lang = gr.Dropdown(choices=["English", "Urdu"], value="English", label="Summary language")
280
- audio_input = gr.Audio(type="filepath", label="Upload or Record audio (optional)")
281
- process_btn = gr.Button("Transcribe & Summarize")
 
282
 
283
- status_box = gr.Textbox(label="Status")
284
- transcript_box = gr.Textbox(label="Transcription", lines=10)
285
- summary_box = gr.Textbox(label="Summary", lines=8)
286
 
287
- process_btn.click(fn=process_input, inputs=[youtube_input, audio_input, summary_lang], outputs=[status_box, transcript_box, summary_box])
288
 
289
- if __name__ == "__main__":
290
- demo.launch()
 
 
1
  import os
2
  import gradio as gr
3
  import tempfile
 
 
 
 
4
  import yt_dlp
5
+ import subprocess
6
  from pydub import AudioSegment
7
  from groq import Groq
8
 
9
+ # ✅ Environment Variables
 
 
10
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
11
+ if not GROQ_API_KEY:
12
+ raise EnvironmentError("Please set GROQ_API_KEY.")
13
 
 
 
 
 
14
  groq_client = Groq(api_key=GROQ_API_KEY)
15
 
16
+ # ✅ Download YouTube Audio
 
 
 
17
  def download_youtube_audio(youtube_url):
18
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp_file:
19
+ ydl_opts = {
20
+ "format": "bestaudio/best",
21
+ "outtmpl": tmp_file.name,
22
+ "quiet": True,
23
+ "postprocessors": [{
24
+ "key": "FFmpegExtractAudio",
25
+ "preferredcodec": "mp3",
26
+ "preferredquality": "192",
27
+ }],
28
+ }
 
 
 
 
 
 
29
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
30
+ ydl.download([youtube_url])
31
+ return tmp_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ # ✅ Convert to WAV safely
34
  def convert_to_wav(input_path):
 
 
 
 
 
 
 
 
35
  try:
36
  tmp_wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False, prefix="conv_")
 
37
  out_wav = tmp_wav.name
 
 
38
  result = subprocess.run(
39
  ["ffmpeg", "-y", "-i", str(input_path), "-ar", "16000", "-ac", "1", out_wav],
40
  stdout=subprocess.PIPE,
41
  stderr=subprocess.PIPE
42
  )
43
+ if result.returncode != 0:
44
+ audio = AudioSegment.from_file(input_path)
45
+ audio = audio.set_frame_rate(16000).set_channels(1)
46
+ audio.export(out_wav, format="wav")
 
 
 
 
 
 
47
  return out_wav
 
48
  except Exception as e:
49
+ raise RuntimeError(f" Error converting to WAV: {e}")
50
 
51
+ # Split long audio into 5-min chunks
52
+ def split_audio(file_path, max_duration_ms=5*60*1000):
53
+ audio = AudioSegment.from_file(file_path)
 
 
 
54
  chunks = []
55
+ for i in range(0, len(audio), max_duration_ms):
56
+ chunk = audio[i:i + max_duration_ms]
57
+ temp_chunk = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
58
+ chunk.export(temp_chunk.name, format="wav")
59
+ chunks.append(temp_chunk.name)
 
60
  return chunks
61
 
62
+ # ✅ Transcription using Groq Whisper
63
+ def transcribe_audio(audio_path):
 
 
 
 
 
 
 
 
64
  try:
65
+ chunks = split_audio(audio_path)
66
+ transcript = ""
67
+ for chunk in chunks:
68
+ with open(chunk, "rb") as f:
69
+ response = groq_client.audio.transcriptions.create(
 
 
 
 
 
70
  model="whisper-large-v3",
71
  file=f
72
  )
73
+ transcript += response.text + "\n"
74
+ return transcript.strip()
 
 
 
 
 
 
 
 
 
75
  except Exception as e:
76
  return f"❌ Error during transcription: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ # Chunk-wise summarization using Groq LLM
79
+ def summarize_text(text, lang):
 
 
 
 
 
 
 
 
 
80
  try:
81
+ chunks = [text[i:i+2000] for i in range(0, len(text), 2000)]
82
+ summaries = []
83
+
84
+ for idx, chunk in enumerate(chunks):
85
+ prompt = (
86
+ f"Summarize the following text in English:\n\n{chunk}"
87
+ if lang == "English"
88
+ else f"مندرجہ ذیل عبارت کا جامع اور رواں اردو خلاصہ تحریر کریں:\n\n{chunk}"
89
+ )
90
+ response = groq_client.chat.completions.create(
91
+ model="openai/gpt-oss-120b",
92
+ messages=[{"role": "user", "content": prompt}],
93
+ temperature=0.6,
94
+ )
95
+ summaries.append(response.choices[0].message.content.strip())
96
+
97
+ # Meta-summary
98
+ combined = "\n".join(summaries)
99
+ final_prompt = (
100
+ f"Combine and condense these summaries into one clear, fluent English summary:\n\n{combined}"
101
+ if lang == "English"
102
+ else f"مندرجہ ذیل خلاصوں کو یکجا کر کے ایک مختصر مگر جامع اردو خلاصہ تحریر کریں:\n\n{combined}"
103
+ )
104
+ final_response = groq_client.chat.completions.create(
105
+ model="openai/gpt-oss-120b",
106
+ messages=[{"role": "user", "content": final_prompt}],
107
+ temperature=0.6,
108
+ )
109
+ return final_response.choices[0].message.content.strip()
 
 
110
  except Exception as e:
111
+ return f"❌ Summarization failed: {e}"
 
 
 
 
112
 
113
+ # Main Function
114
+ def process_input(youtube_url, audio_file, lang):
 
 
 
 
 
 
115
  try:
116
+ if youtube_url:
117
+ audio_path = download_youtube_audio(youtube_url)
118
+ elif audio_file:
119
+ audio_path = audio_file
 
 
 
 
120
  else:
121
+ return "❌ Please upload an audio or paste YouTube link.", "", ""
122
 
123
+ wav_path = convert_to_wav(audio_path)
124
+ transcript = transcribe_audio(wav_path)
125
  if transcript.startswith("❌"):
126
  return transcript, "", ""
127
 
128
+ summary = summarize_text(transcript, lang)
129
+ return "✅ Transcription Completed!", transcript, summary
130
+ except Exception as e:
131
+ return f"❌ Error: {e}", "", ""
 
 
 
 
 
 
 
 
 
 
132
 
133
+ # ✅ Gradio Interface
134
+ with gr.Blocks(title="🎧 Urdu & English Audio Summarizer") as app:
135
+ gr.Markdown("## 🎧 Transcribe & Summarize English or Urdu Audio / YouTube Videos")
 
 
 
 
136
 
 
 
 
 
 
137
  with gr.Row():
138
+ youtube_link = gr.Textbox(label="📺 YouTube Link (optional)")
139
+ lang_choice = gr.Dropdown(["English", "Urdu"], value="English", label="🌐 Summary Language")
140
+
141
+ audio_input = gr.Audio(type="filepath", label="🎙️ Upload Audio (optional)")
142
+ btn = gr.Button("🚀 Transcribe & Summarize")
143
 
144
+ status = gr.Textbox(label="Status")
145
+ transcript_box = gr.Textbox(label="📝 Transcription", lines=8)
146
+ summary_box = gr.Textbox(label="🧩 Summary", lines=8)
147
 
148
+ btn.click(process_input, [youtube_link, audio_input, lang_choice], [status, transcript_box, summary_box])
149
 
150
+ app.launch()