Turbiling commited on
Commit
57bf8f4
·
verified ·
1 Parent(s): 923e40d

Update app.py

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