Turbiling commited on
Commit
5e0c096
·
verified ·
1 Parent(s): bf56731

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +300 -0
app.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ # SmartTranscribe - Single-file Hugging Face Space (Gradio)
3
+ # Requirements (put in requirements.txt for the Space):
4
+ # gradio>=3.0
5
+ # requests
6
+ # yt-dlp
7
+ # python-dotenv
8
+ # ffmpeg (system package, usually present on Spaces)
9
+ #
10
+ # Environment variables required:
11
+ # GROQ_API_KEY
12
+ # HUGGINGFACE_API_TOKEN
13
+
14
+ import os
15
+ import tempfile
16
+ import subprocess
17
+ import json
18
+ from pathlib import Path
19
+
20
+ import requests
21
+ import gradio as gr
22
+ import yt_dlp
23
+
24
+ # --------- Configuration / Endpoints ----------
25
+ GROQ_TRANSCRIPTION_URL = "https://api.groq.com/openai/v1/audio/transcriptions"
26
+ HUGGINGFACE_INFERENCE_URL = "https://api-inference.huggingface.co/models/openai/gpt-oss-120b"
27
+
28
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
29
+ HUGGINGFACE_API_TOKEN = os.environ.get("HUGGINGFACE_API_TOKEN")
30
+
31
+ if not GROQ_API_KEY or not HUGGINGFACE_API_TOKEN:
32
+ raise EnvironmentError(
33
+ "Environment variables GROQ_API_KEY and HUGGINGFACE_API_TOKEN must be set."
34
+ )
35
+
36
+ # --------- Helper utilities ---------
37
+
38
+ def download_youtube_audio(youtube_url: str, out_path: str) -> str:
39
+ """
40
+ Download best audio from YouTube and convert to WAV using yt-dlp + ffmpeg.
41
+ Returns path to the WAV file.
42
+ """
43
+ out_base = out_path
44
+ ydl_opts = {
45
+ "format": "bestaudio/best",
46
+ "outtmpl": out_base + ".%(ext)s",
47
+ "quiet": True,
48
+ "no_warnings": True,
49
+ "postprocessors": [
50
+ {
51
+ "key": "FFmpegExtractAudio",
52
+ "preferredcodec": "wav",
53
+ "preferredquality": "192",
54
+ }
55
+ ],
56
+ }
57
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
58
+ ydl.extract_info(youtube_url, download=True)
59
+ wav_path = out_base + ".wav"
60
+ if not Path(wav_path).exists():
61
+ raise FileNotFoundError("YouTube audio download failed or ffmpeg postprocessing missing.")
62
+ return wav_path
63
+
64
+ def convert_to_wav(input_path: str) -> str:
65
+ """
66
+ Convert input file to 16kHz mono WAV (overwrites if same name exists).
67
+ If input already .wav, returns it.
68
+ """
69
+ p = Path(input_path)
70
+ if p.suffix.lower() == ".wav":
71
+ return input_path
72
+ out = str(p.with_suffix(".wav"))
73
+ cmd = [
74
+ "ffmpeg",
75
+ "-y",
76
+ "-i",
77
+ str(input_path),
78
+ "-ar",
79
+ "16000",
80
+ "-ac",
81
+ "1",
82
+ out,
83
+ ]
84
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
85
+ return out
86
+
87
+ def transcribe_with_groq(audio_wav_path: str, api_key: str, language: str = None) -> dict:
88
+ """
89
+ Send audio file to Groq transcription endpoint.
90
+ language: 'ur' or 'en' or None/'auto'
91
+ Returns JSON response (expects at least 'text' or similar).
92
+ """
93
+ headers = {"Authorization": f"Bearer {api_key}"}
94
+ data = {}
95
+ if language and language != "auto":
96
+ # pass human-friendly label; endpoint handlers may vary
97
+ data["language"] = "urdu" if language == "ur" else "english"
98
+ # If we want special behavior for Urdu, add prompt
99
+ if data.get("language") == "urdu":
100
+ data["prompt"] = (
101
+ "Transcribe speech in Urdu. If English words are present in the Urdu audio, "
102
+ "write them using Urdu script (مثلاً 'school' -> 'اسکول'). "
103
+ "Preserve correct Urdu punctuation and grammar."
104
+ )
105
+ files = {"file": open(audio_wav_path, "rb")}
106
+ try:
107
+ resp = requests.post(GROQ_TRANSCRIPTION_URL, headers=headers, data=data, files=files, timeout=120)
108
+ resp.raise_for_status()
109
+ return resp.json()
110
+ finally:
111
+ files["file"].close()
112
+
113
+ def hf_chat_completion(prompt: str, hf_token: str, max_tokens: int = 512) -> str:
114
+ """
115
+ Simple wrapper to call Hugging Face Inference API for openai/gpt-oss-120b
116
+ """
117
+ headers = {"Authorization": f"Bearer {hf_token}", "Content-Type": "application/json"}
118
+ payload = {"inputs": prompt, "parameters": {"max_new_tokens": max_tokens}}
119
+ r = requests.post(HUGGINGFACE_INFERENCE_URL, headers=headers, json=payload, timeout=180)
120
+ r.raise_for_status()
121
+ out = r.json()
122
+ # Standard HF inference output handlers
123
+ if isinstance(out, dict) and "generated_text" in out:
124
+ return out["generated_text"]
125
+ if isinstance(out, list) and len(out) > 0 and isinstance(out[0], dict) and "generated_text" in out[0]:
126
+ return out[0]["generated_text"]
127
+ if isinstance(out, str):
128
+ return out
129
+ return json.dumps(out)
130
+
131
+ def normalize_urdu_text_with_gpt(transcript: str, hf_token: str) -> str:
132
+ """
133
+ Use GPT-OSS to convert embedded English words to Urdu script and fix punctuation/grammar.
134
+ Returns normalized Urdu text only.
135
+ """
136
+ prompt = (
137
+ "You are an expert in Urdu orthography and transliteration.\n"
138
+ "Task: Convert the following Urdu transcription into correct, well-punctuated Urdu script.\n"
139
+ "Whenever English words appear inside the Urdu text, transliterate them into Urdu alphabets "
140
+ "(for example: 'school' -> 'اسکول') while preserving meaning and grammar.\n"
141
+ "Return only the corrected Urdu transcription — do not include any explanations.\n\n"
142
+ "Transcription:\n" + transcript + "\n\nCorrected transcription:"
143
+ )
144
+ return hf_chat_completion(prompt, hf_token, max_tokens=1024)
145
+
146
+ def summarize_with_gpt(text: str, hf_token: str) -> str:
147
+ """
148
+ Summarize input text using GPT-OSS-120B. Keeps the language the same as input.
149
+ Returns short summary and bullet key-takeaways.
150
+ """
151
+ prompt = (
152
+ "Summarize the following text. Output a short summary (3-6 sentences) "
153
+ "followed by bullet-point key takeaways. Keep the language the same as the input.\n\n"
154
+ "Text:\n" + text
155
+ )
156
+ return hf_chat_completion(prompt, hf_token, max_tokens=256)
157
+
158
+ # --------- Core processing pipeline ---------
159
+
160
+ def process_audio_file(audio_path: str, force_language: str = "auto"):
161
+ """
162
+ Full pipeline:
163
+ - ensure WAV
164
+ - transcribe via Groq
165
+ - if Urdu (detected or forced), normalize English words to Urdu script using GPT
166
+ - summarize using GPT-OSS
167
+ Returns dict: transcript (raw), normalized_transcript, summary, detected_language
168
+ """
169
+ wav_path = convert_to_wav(audio_path)
170
+ try:
171
+ groq_resp = transcribe_with_groq(wav_path, api_key=GROQ_API_KEY, language=force_language)
172
+ except Exception as e:
173
+ return {"error": f"Transcription error: {e}"}
174
+
175
+ # Groq response shape may vary; attempt to extract text and language
176
+ transcript = ""
177
+ detected_language = None
178
+ if isinstance(groq_resp, dict):
179
+ # common keys: text, transcription
180
+ transcript = groq_resp.get("text") or groq_resp.get("transcription") or groq_resp.get("result") or ""
181
+ # sometimes 'language' may be provided
182
+ detected_language = groq_resp.get("language") or groq_resp.get("detected_language") or None
183
+ # fallback: if transcript is nested
184
+ if not transcript and "segments" in groq_resp and isinstance(groq_resp["segments"], list):
185
+ transcript = " ".join([seg.get("text", "") for seg in groq_resp["segments"]])
186
+ elif isinstance(groq_resp, str):
187
+ transcript = groq_resp
188
+
189
+ normalized = transcript
190
+ # Decide whether to normalize to Urdu script:
191
+ do_urdu_normalize = False
192
+ if force_language == "ur":
193
+ do_urdu_normalize = True
194
+ elif detected_language and isinstance(detected_language, str) and detected_language.lower().startswith("ur"):
195
+ do_urdu_normalize = True
196
+ # If transcript contains significant Urdu characters, we may still want normalization,
197
+ # but we rely on explicit detection/force for reliability.
198
+ if do_urdu_normalize and transcript.strip():
199
+ try:
200
+ normalized = normalize_urdu_text_with_gpt(transcript, HUGGINGFACE_API_TOKEN)
201
+ except Exception as e:
202
+ normalized = transcript + f"\n\n[Normalization failed: {e}]"
203
+
204
+ # Summarize (use normalized text if available)
205
+ to_summarize = normalized if normalized else transcript
206
+ summary = ""
207
+ if to_summarize.strip():
208
+ try:
209
+ summary = summarize_with_gpt(to_summarize, HUGGINGFACE_API_TOKEN)
210
+ except Exception as e:
211
+ summary = f"Summary failed: {e}"
212
+
213
+ return {
214
+ "transcript": transcript,
215
+ "normalized_transcript": normalized,
216
+ "summary": summary,
217
+ "detected_language": detected_language,
218
+ }
219
+
220
+ # --------- Gradio UI callbacks ---------
221
+
222
+ def transcribe_upload(file, language_choice):
223
+ """
224
+ file: path to uploaded/recorded file (gr.Audio returns a file path)
225
+ language_choice: 'auto', 'ur', 'en'
226
+ """
227
+ if not file:
228
+ return "", "", "براہِ مہربانی آڈیو/ویڈیو فائل اپلوڈ کریں۔"
229
+ try:
230
+ result = process_audio_file(file, force_language=language_choice)
231
+ except Exception as e:
232
+ return "", "", f"پروسیسنگ میں خرابی: {e}"
233
+ if "error" in result:
234
+ return "", "", result["error"]
235
+ return result.get("transcript", ""), result.get("normalized_transcript", ""), result.get("summary", "")
236
+
237
+ def transcribe_youtube(youtube_url, language_choice):
238
+ if not youtube_url:
239
+ return "", "", "براہِ مہربانی YouTube کا URL فراہم کریں۔"
240
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
241
+ out_base = tmp.name[:-4] # remove .wav
242
+ tmp.close()
243
+ try:
244
+ wav_path = download_youtube_audio(youtube_url, out_base)
245
+ result = process_audio_file(wav_path, force_language=language_choice)
246
+ except Exception as e:
247
+ return "", "", f"YouTube یا کنورژن میں خرابی: {e}"
248
+ finally:
249
+ # cleanup downloaded file(s)
250
+ try:
251
+ if os.path.exists(out_base + ".wav"):
252
+ os.remove(out_base + ".wav")
253
+ except:
254
+ pass
255
+ if "error" in result:
256
+ return "", "", result["error"]
257
+ return result.get("transcript", ""), result.get("normalized_transcript", ""), result.get("summary", "")
258
+
259
+ # --------- Gradio App Layout ---------
260
+
261
+ title_md = """
262
+ # SmartTranscribe — اردو / English ٹرانسکرِپشن + خلاصہ
263
+ یہ ایپ آڈیو یا ویڈیو کو اسی زبان میں متن میں بدلتی ہے۔ اگر اردو ریکارڈنگ میں انگریزی الفاظ ہوں تو وہ اردو رسمِ حروف میں لکھے جائیں گے، پھر خلاصہ `openai/gpt-oss-120b` سے بنایا جائے گا۔
264
+ """
265
+
266
+ with gr.Blocks(title="SmartTranscribe") as demo:
267
+ gr.Markdown(title_md)
268
+
269
+ with gr.Tab("Upload / Record"):
270
+ gr.Markdown("اپنی فائل اپلوڈ کریں یا براہِ راست ریکارڈ کریں:")
271
+ audio_input = gr.Audio(source="upload", type="filepath", label="Upload / Record audio or video")
272
+ language = gr.Radio(["auto", "ur", "en"], value="auto", label="زبان منتخب کریں (عام حالت: auto)")
273
+ transcribe_btn = gr.Button("Transcribe & Summarize")
274
+
275
+ out_transcript = gr.Textbox(label="Transcription (raw)", lines=8)
276
+ out_normalized = gr.Textbox(label="Transcription (normalized — Urdu script when applicable)", lines=8)
277
+ out_summary = gr.Textbox(label="Summary (GPT-OSS)", lines=6)
278
+
279
+ transcribe_btn.click(fn=transcribe_upload, inputs=[audio_input, language], outputs=[out_transcript, out_normalized, out_summary])
280
+
281
+ with gr.Tab("YouTube Link"):
282
+ gr.Markdown("YouTube کا URL پیسٹ کریں:")
283
+ yt_url = gr.Textbox(label="YouTube URL")
284
+ yt_lang = gr.Radio(["auto", "ur", "en"], value="auto", label="زبان منتخب کریں")
285
+ yt_btn = gr.Button("Fetch, Transcribe & Summarize")
286
+
287
+ yt_out_transcript = gr.Textbox(label="Transcription (raw)", lines=8)
288
+ yt_out_normalized = gr.Textbox(label="Transcription (normalized)", lines=8)
289
+ yt_out_summary = gr.Textbox(label="Summary (GPT-OSS)", lines=6)
290
+
291
+ yt_btn.click(fn=transcribe_youtube, inputs=[yt_url, yt_lang], outputs=[yt_out_transcript, yt_out_normalized, yt_out_summary])
292
+
293
+ gr.Markdown("""
294
+ ---
295
+ **ہدایات:** Space سیٹنگز میں `GROQ_API_KEY` اور `HUGGINGFACE_API_TOKEN` بطور Secrets شامل کریں۔
296
+ لمبی فائلز کے لیے ریئل-ٹائم کی جگہ چنکس بنائے جائیں گے — پروسیسنگ کا وقت ماڈیولز اور لمبائی پر منحصر ہوگا۔
297
+ """)
298
+
299
+ if __name__ == "__main__":
300
+ demo.launch()