NoLev commited on
Commit
e606742
·
verified ·
1 Parent(s): 09967ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +235 -206
app.py CHANGED
@@ -6,6 +6,16 @@ import re
6
  import tempfile
7
  import os
8
  import xml.etree.ElementTree as ET
 
 
 
 
 
 
 
 
 
 
9
 
10
  # Global cache for pipelines to avoid reloading models
11
  pipelines = {}
@@ -31,225 +41,243 @@ def get_pipeline(model_id):
31
  )
32
  return pipelines[model_id]
33
 
34
- # Updated function to fetch MP3 from Apple Podcasts episode URL via iTunes API + RSS
35
- def fetch_podcast_mp3(podcast_url):
36
- if not podcast_url or "podcasts.apple.com" not in podcast_url:
37
- return None, "Invalid Apple Podcasts URL. Please use a valid episode link (e.g., https://podcasts.apple.com/...)."
38
-
39
- # Parse podcast and episode IDs
40
- podcast_match = re.search(r'id(\d+)', podcast_url)
41
- if not podcast_match:
42
- return None, "Invalid URL: No podcast ID found."
43
- podcast_id = podcast_match.group(1)
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- episode_match = re.search(r'i=(\d+)', podcast_url)
46
- if not episode_match:
47
- return None, "Invalid URL: No episode ID found."
48
- episode_id = episode_match.group(1)
49
 
50
  headers = {"User-Agent": "Mozilla/5.0 (compatible; PodcastTranscriber/1.0)"}
51
 
52
  try:
53
- # Step 1: Get RSS feed URL via iTunes Lookup API
54
- api_url = f"https://itunes.apple.com/lookup?id={podcast_id}&entity=podcast"
55
- api_response = requests.get(api_url, headers=headers)
56
- api_response.raise_for_status()
57
- data = api_response.json()
58
-
59
- if data['resultCount'] == 0:
60
- return None, "Podcast not found in iTunes catalog."
61
-
62
- feed_url = data['results'][0]['feedUrl']
63
-
64
- # Step 2: Fetch and parse RSS XML
65
- rss_response = requests.get(feed_url, headers=headers)
66
- rss_response.raise_for_status()
67
- root = ET.fromstring(rss_response.content)
68
-
69
- ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
70
- mp3_url = None
71
 
72
- for item in root.findall('.//item'):
73
- episode_elem = item.find('itunes:episode', ns)
74
- if episode_elem is not None and episode_elem.text == episode_id:
75
- enclosure = item.find('enclosure')
76
- if enclosure is not None:
77
- mp3_url = enclosure.get('url')
78
- break
79
 
80
- if not mp3_url:
81
- return None, "Episode not found in RSS feed (may be private or ID mismatch)."
82
-
83
- # Step 3: Download MP3 to temp file
84
- mp3_response = requests.get(mp3_url, headers=headers, stream=True)
85
- mp3_response.raise_for_status()
86
-
87
- total_size = int(mp3_response.headers.get('content-length', 0))
88
  downloaded = 0
 
89
 
90
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
91
- for chunk in mp3_response.iter_content(chunk_size=1024 * 1024): # 1MB chunks
92
  if chunk:
93
  tmp_file.write(chunk)
94
  downloaded += len(chunk)
95
 
96
  temp_path = tmp_file.name
97
  size_mb = downloaded / (1024 * 1024)
98
-
99
- return temp_path, f"Downloaded episode: {size_mb:.1f} MB from {mp3_url}"
100
 
101
  except Exception as e:
102
- return None, f"Error fetching MP3: {str(e)}"
103
 
104
- # Transcription function as generator for progress updates
105
- def transcribe_speech(audio_input, model_id, language="english", return_timestamps=False, podcast_url=None):
106
  audio_file = None
107
- status = gr.Markdown("Ready to transcribe! 💬")
108
 
109
- if podcast_url:
110
- # Fetch MP3 with progress yields
111
- yield "", gr.Markdown("**Fetching podcast metadata via iTunes API...**")
112
-
113
- # Simulate fetch here, but actual fetch is called below - yields are in main flow
114
- audio_file, fetch_msg = fetch_podcast_mp3(podcast_url)
115
- if not audio_file:
116
- yield fetch_msg, gr.Markdown(f"**Error: {fetch_msg}**")
117
- return
118
-
119
- # Note: fetch_msg includes size; display it
120
- yield "", gr.Markdown(f"**{fetch_msg}**")
121
-
122
- # Download progress is now in fetch_podcast_mp3? Wait, no - I moved stream to main for yields.
123
- # Actually, to yield during download, move the download logic here.
124
-
125
- # Redo: Move download to here for progress
126
- # ... (parse IDs and get mp3_url as above, but to avoid dup code, call a helper)
127
-
128
- # For brevity, assume fetch returns mp3_url, then download here.
129
- # But to fix, I'll inline the download progress in this generator.
130
-
131
- # Inline fetch logic for progress (condensed)
132
- podcast_match = re.search(r'id(\d+)', podcast_url)
133
- if not podcast_match:
134
- yield "Invalid URL: No podcast ID.", status
135
- return
136
- podcast_id = podcast_match.group(1)
137
-
138
- episode_match = re.search(r'i=(\d+)', podcast_url)
139
- if not episode_match:
140
- yield "Invalid URL: No episode ID.", status
141
- return
142
- episode_id = episode_match.group(1)
143
-
144
- headers = {"User-Agent": "Mozilla/5.0 (compatible; PodcastTranscriber/1.0)"}
145
-
146
- yield "", gr.Markdown("**Looking up podcast in iTunes...**")
147
-
148
- api_url = f"https://itunes.apple.com/lookup?id={podcast_id}&entity=podcast"
149
- api_response = requests.get(api_url, headers=headers)
150
- api_response.raise_for_status()
151
- data = api_response.json()
152
-
153
- if data['resultCount'] == 0:
154
- yield "Podcast not found.", gr.Markdown("**Podcast not found in iTunes.**")
155
- return
156
-
157
- feed_url = data['results'][0]['feedUrl']
158
- yield "", gr.Markdown("**Fetching RSS feed...**")
159
-
160
- rss_response = requests.get(feed_url, headers=headers)
161
- rss_response.raise_for_status()
162
- root = ET.fromstring(rss_response.content)
163
-
164
- ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
165
- mp3_url = None
166
-
167
- yield "", gr.Markdown("**Finding episode in RSS...**")
168
-
169
- for item in root.findall('.//item'):
170
- episode_elem = item.find('itunes:episode', ns)
171
- if episode_elem is not None and episode_elem.text == episode_id:
172
- enclosure = item.find('enclosure')
173
- if enclosure is not None:
174
- mp3_url = enclosure.get('url')
175
- break
176
-
177
- if not mp3_url:
178
- yield "Episode not found in RSS.", gr.Markdown("**Episode not found (may be private).**")
179
- return
180
-
181
- yield "", gr.Markdown("**Downloading MP3...**")
 
 
182
 
183
- mp3_response = requests.get(mp3_url, headers=headers, stream=True)
184
- mp3_response.raise_for_status()
 
 
 
185
 
186
- total_size = int(mp3_response.headers.get('content-length', 0))
187
- downloaded = 0
 
 
 
188
 
189
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
190
- for chunk in mp3_response.iter_content(chunk_size=1024 * 1024):
191
- if chunk:
192
- tmp_file.write(chunk)
193
- downloaded += len(chunk)
194
- if total_size > 0:
195
- percent = (downloaded / total_size) * 100
196
- yield "", gr.Markdown(f"**Downloading: {percent:.1f}% ({downloaded / (1024*1024):.1f} MB / {total_size / (1024*1024):.1f} MB)**")
197
- else:
198
- yield "", gr.Markdown(f"**Downloading: {downloaded / (1024*1024):.1f} MB...**")
199
 
200
- audio_file = tmp_file.name
201
- size_mb = downloaded / (1024 * 1024)
202
- yield "", gr.Markdown(f"**Download complete: {size_mb:.1f} MB. Starting transcription...**")
203
-
204
- else:
205
- # Uploaded file
206
- if audio_input is None:
207
- yield "Please upload an audio file.", gr.Markdown("**No file uploaded.**")
208
- return
209
- audio_file = audio_input
210
- yield "", gr.Markdown("**Starting transcription on uploaded file...**")
211
-
212
- # Transcribe
213
- try:
214
  pipe = get_pipeline(model_id)
215
 
216
  generate_kwargs = {"task": "transcribe", "language": language}
217
- if return_timestamps:
218
- generate_kwargs["return_timestamps"] = True
219
-
220
- yield "", gr.Markdown("**Transcribing audio... (progress depends on file length and hardware)**")
221
 
222
- output = pipe(
223
- audio_file,
224
- max_new_tokens=128,
225
- generate_kwargs=generate_kwargs,
226
- chunk_length_s=30,
227
- stride_length_s=5,
228
- batch_size=8 if "tiny" not in model_id and "base" not in model_id else 16,
229
- return_timestamps=return_timestamps,
230
- )
231
-
232
- # Clean up temp file
233
- if podcast_url and os.path.exists(audio_file):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  os.unlink(audio_file)
235
 
236
- if return_timestamps and "chunks" in output:
237
  formatted = []
238
- for chunk in output["chunks"]:
239
  start = f"{chunk['timestamp'][0]:.2f}s" if chunk['timestamp'][0] is not None else "0.00s"
240
  end = f"{chunk['timestamp'][1]:.2f}s" if chunk['timestamp'][1] is not None else "?.?s"
241
  formatted.append(f"[{start} - {end}] {chunk['text']}")
242
- text = "\n".join(formatted)
243
  else:
244
- text = output["text"]
245
 
246
- yield text, gr.Markdown("**Transcription complete! 🎉 Copy or download the text above.**")
 
 
247
 
248
  except Exception as e:
249
- if podcast_url and os.path.exists(audio_file):
250
- os.unlink(audio_file)
251
- error_msg = f"Transcription error: {str(e)}"
252
- yield error_msg, gr.Markdown(f"**{error_msg}**")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  # Create the Gradio app with a colorful, responsive theme
255
  theme = gr.themes.Soft(
@@ -263,15 +291,16 @@ with gr.Blocks(theme=theme, title="MP3 to Text Transcriber") as demo:
263
  gr.Markdown(
264
  """
265
  # 🎤 MP3 to Text Transcription Tool
266
- Upload an MP3 (or any audio file) **or** paste an Apple Podcasts episode URL to fetch and transcribe it automatically!
267
- Now with real-time progress for downloads and transcription. Supports long files up to hours.
 
268
  """,
269
  elem_classes=["centered"]
270
  )
271
 
272
  with gr.Row(variant="panel", elem_classes=["max-w-4xl mx-auto"]):
273
  with gr.Column(scale=1):
274
- # Option 1: File upload
275
  audio_input = gr.Audio(
276
  sources="upload",
277
  type="filepath",
@@ -279,13 +308,18 @@ with gr.Blocks(theme=theme, title="MP3 to Text Transcriber") as demo:
279
  elem_classes=["w-full"]
280
  )
281
 
282
- # Option 2: Podcast URL
283
  podcast_input = gr.Textbox(
284
  label="🔗 Apple Podcasts Episode URL (optional)",
285
  placeholder="e.g., https://podcasts.apple.com/us/podcast/.../id123?i=456",
286
  elem_classes=["w-full"]
287
  )
288
 
 
 
 
 
 
 
289
  model_dropdown = gr.Dropdown(
290
  choices=MODEL_OPTIONS,
291
  value=MODEL_OPTIONS[1],
@@ -309,36 +343,31 @@ with gr.Blocks(theme=theme, title="MP3 to Text Transcriber") as demo:
309
  )
310
 
311
  with gr.Column(scale=1):
312
- status_output = gr.Markdown("Ready to transcribe! 💬", elem_classes=["text-center"])
313
 
314
  # Buttons
315
  with gr.Row(elem_classes=["w-full"]):
316
- transcribe_btn = gr.Button("🚀 Transcribe Uploaded File", variant="secondary", elem_classes=["flex-1"])
317
- podcast_btn = gr.Button("📡 Fetch & Transcribe Podcast", variant="primary", elem_classes=["flex-1"])
 
318
 
319
- transcript_output = gr.Textbox(
320
- label="📝 Transcript",
321
- lines=15,
322
- max_lines=20,
323
- placeholder="Your transcription will appear here...",
324
- elem_classes=["w-full", "bg-gray-50 dark:bg-gray-800"],
325
- show_copy_button=True
326
- )
327
-
328
- # Event handlers (generator fns auto-handle progress updates)
329
  transcribe_btn.click(
330
- fn=transcribe_speech,
331
- inputs=[audio_input, model_dropdown, language_dropdown, timestamps_checkbox, gr.State(None)], # podcast_url=None
332
- outputs=[transcript_output, status_output],
333
- show_progress="full" # Enables determinate-ish progress for generators
334
  )
335
 
336
- # For podcast, pass podcast_input as podcast_url
337
  podcast_btn.click(
338
- fn=transcribe_speech,
339
- inputs=[gr.State(None), model_dropdown, language_dropdown, timestamps_checkbox, podcast_input], # audio=None, podcast_url=input
340
- outputs=[transcript_output, status_output],
341
- show_progress="full"
 
 
 
 
 
342
  )
343
 
344
  if __name__ == "__main__":
 
6
  import tempfile
7
  import os
8
  import xml.etree.ElementTree as ET
9
+ import torchaudio
10
+ import concurrent.futures
11
+ import uuid
12
+
13
+ # Load Telegram credentials from env vars
14
+ TELEGRAM_TOKEN = os.environ.get('TELEGRAM_TOKEN')
15
+ TELEGRAM_CHAT_ID = os.environ.get('TELEGRAM_CHAT_ID')
16
+
17
+ if not TELEGRAM_TOKEN or not TELEGRAM_CHAT_ID:
18
+ raise ValueError("TELEGRAM_TOKEN and TELEGRAM_CHAT_ID must be set as environment variables in HF Space settings.")
19
 
20
  # Global cache for pipelines to avoid reloading models
21
  pipelines = {}
 
41
  )
42
  return pipelines[model_id]
43
 
44
+ # Function to send message to Telegram
45
+ def send_to_telegram(message):
46
+ url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
47
+ payload = {
48
+ "chat_id": TELEGRAM_CHAT_ID,
49
+ "text": message,
50
+ "parse_mode": "Markdown"
51
+ }
52
+ try:
53
+ response = requests.post(url, json=payload)
54
+ response.raise_for_status()
55
+ return True
56
+ except Exception as e:
57
+ print(f"Telegram send error: {e}")
58
+ return False
59
+
60
+ # Function to fetch MP3 from Google Drive shareable link
61
+ def fetch_from_google_drive(drive_link):
62
+ match = re.search(r'/d/([a-zA-Z0-9_-]+)', drive_link)
63
+ if not match:
64
+ return None, "Invalid Google Drive link. Use a shareable link like https://drive.google.com/file/d/FILE_ID/view."
65
 
66
+ file_id = match.group(1)
67
+ download_url = f"https://drive.google.com/uc?export=download&id={file_id}"
 
 
68
 
69
  headers = {"User-Agent": "Mozilla/5.0 (compatible; PodcastTranscriber/1.0)"}
70
 
71
  try:
72
+ response = requests.get(download_url, headers=headers, stream=True, allow_redirects=True)
73
+ if "confirm" in response.url:
74
+ confirm_match = re.search(r'confirm=([0-9A-Za-z_-]+)', response.url)
75
+ if confirm_match:
76
+ confirm_token = confirm_match.group(1)
77
+ download_url = f"https://drive.google.com/uc?export=download&confirm={confirm_token}&id={file_id}"
78
+ response = requests.get(download_url, headers=headers, stream=True)
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ response.raise_for_status()
 
 
 
 
 
 
81
 
82
+ total_size = int(response.headers.get('content-length', 0))
 
 
 
 
 
 
 
83
  downloaded = 0
84
+ chunk_size = 1024 * 1024
85
 
86
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
87
+ for chunk in response.iter_content(chunk_size=chunk_size):
88
  if chunk:
89
  tmp_file.write(chunk)
90
  downloaded += len(chunk)
91
 
92
  temp_path = tmp_file.name
93
  size_mb = downloaded / (1024 * 1024)
94
+ return temp_path, f"Downloaded from Drive: {size_mb:.1f} MB"
 
95
 
96
  except Exception as e:
97
+ return None, f"Error fetching from Drive: {str(e)} (Ensure the file is shared publicly or with 'Anyone with the link')"
98
 
99
+ # Background transcription task
100
+ def background_transcribe(task_id, audio_input, model_id, language, return_timestamps, podcast_url, drive_link):
101
  audio_file = None
102
+ status_msg = f"Task {task_id}: Starting..."
103
 
104
+ try:
105
+ if drive_link:
106
+ audio_file, msg = fetch_from_google_drive(drive_link)
107
+ if not audio_file:
108
+ send_to_telegram(f"Task {task_id} failed: {msg}")
109
+ return
110
+ status_msg += f"\n{msg}"
111
+
112
+ elif podcast_url:
113
+ podcast_match = re.search(r'id(\d+)', podcast_url)
114
+ if not podcast_match:
115
+ send_to_telegram(f"Task {task_id} failed: Invalid URL: No podcast ID.")
116
+ return
117
+ podcast_id = podcast_match.group(1)
118
+
119
+ episode_match = re.search(r'i=(\d+)', podcast_url)
120
+ if not episode_match:
121
+ send_to_telegram(f"Task {task_id} failed: Invalid URL: No episode ID.")
122
+ return
123
+ episode_id = episode_match.group(1)
124
+
125
+ headers = {"User-Agent": "Mozilla/5.0 (compatible; PodcastTranscriber/1.0)"}
126
+
127
+ api_url = f"https://itunes.apple.com/lookup?id={podcast_id}&entity=podcast"
128
+ api_response = requests.get(api_url, headers=headers)
129
+ api_response.raise_for_status()
130
+ data = api_response.json()
131
+
132
+ if data['resultCount'] == 0:
133
+ send_to_telegram(f"Task {task_id} failed: Podcast not found.")
134
+ return
135
+
136
+ feed_url = data['results'][0]['feedUrl']
137
+
138
+ rss_response = requests.get(feed_url, headers=headers)
139
+ rss_response.raise_for_status()
140
+ root = ET.fromstring(rss_response.content)
141
+
142
+ ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
143
+ mp3_url = None
144
+
145
+ for item in root.findall('.//item'):
146
+ episode_guid = item.find('guid')
147
+ if episode_guid is not None and episode_id in episode_guid.text:
148
+ enclosure = item.find('enclosure')
149
+ if enclosure is not None:
150
+ mp3_url = enclosure.get('url')
151
+ break
152
+
153
+ episode_elem = item.find('itunes:episode', ns)
154
+ if episode_elem is not None and episode_elem.text == episode_id:
155
+ enclosure = item.find('enclosure')
156
+ if enclosure is not None:
157
+ mp3_url = enclosure.get('url')
158
+ break
159
+
160
+ if not mp3_url:
161
+ send_to_telegram(f"Task {task_id} failed: Episode not found.")
162
+ return
163
+
164
+ mp3_response = requests.get(mp3_url, headers=headers, stream=True)
165
+ mp3_response.raise_for_status()
166
+
167
+ total_size = int(mp3_response.headers.get('content-length', 0))
168
+ downloaded = 0
169
+
170
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
171
+ for chunk in mp3_response.iter_content(chunk_size=1024 * 1024):
172
+ if chunk:
173
+ tmp_file.write(chunk)
174
+ downloaded += len(chunk)
175
+
176
+ audio_file = tmp_file.name
177
+ size_mb = downloaded / (1024 * 1024)
178
+ status_msg += f"\nDownloaded from podcast: {size_mb:.1f} MB"
179
 
180
+ else:
181
+ if audio_input is None:
182
+ send_to_telegram(f"Task {task_id} failed: No audio provided.")
183
+ return
184
+ audio_file = audio_input
185
 
186
+ waveform, sample_rate = torchaudio.load(audio_file)
187
+ if waveform.shape[0] > 1:
188
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
189
+ num_samples = waveform.shape[1]
190
+ duration = num_samples / sample_rate
191
 
192
+ status_msg += f"\nAudio duration: {duration / 60:.1f} minutes"
 
 
 
 
 
 
 
 
 
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  pipe = get_pipeline(model_id)
195
 
196
  generate_kwargs = {"task": "transcribe", "language": language}
 
 
 
 
197
 
198
+ chunk_length_s = 30
199
+ stride_length_s = 5
200
+ chunk_samples = int(chunk_length_s * sample_rate)
201
+ stride_samples = int(stride_length_s * sample_rate)
202
+
203
+ chunks = []
204
+ offsets = []
205
+ start = 0
206
+ while start < num_samples:
207
+ end = min(start + chunk_samples, num_samples)
208
+ chunks.append(waveform[:, start:end])
209
+ offsets.append(start / sample_rate)
210
+ start += chunk_samples - 2 * stride_samples
211
+
212
+ num_chunks = len(chunks)
213
+ full_text = ""
214
+ all_chunk_outputs = []
215
+
216
+ for i, (chunk, offset) in enumerate(zip(chunks, offsets)):
217
+ output = pipe(
218
+ {"waveform": chunk, "sampling_rate": sample_rate},
219
+ max_new_tokens=128,
220
+ generate_kwargs=generate_kwargs,
221
+ return_timestamps=return_timestamps,
222
+ batch_size=1
223
+ )
224
+
225
+ if return_timestamps and "chunks" in output:
226
+ adjusted_chunks = []
227
+ for ch in output["chunks"]:
228
+ ts = list(ch["timestamp"])
229
+ if ts[0] is not None:
230
+ ts[0] += offset
231
+ if ts[1] is not None:
232
+ ts[1] += offset
233
+ adjusted_chunks.append({"text": ch["text"], "timestamp": tuple(ts)})
234
+ all_chunk_outputs.extend(adjusted_chunks)
235
+ else:
236
+ full_text += output["text"] + " "
237
+
238
+ if os.path.exists(audio_file):
239
  os.unlink(audio_file)
240
 
241
+ if return_timestamps:
242
  formatted = []
243
+ for chunk in all_chunk_outputs:
244
  start = f"{chunk['timestamp'][0]:.2f}s" if chunk['timestamp'][0] is not None else "0.00s"
245
  end = f"{chunk['timestamp'][1]:.2f}s" if chunk['timestamp'][1] is not None else "?.?s"
246
  formatted.append(f"[{start} - {end}] {chunk['text']}")
247
+ transcript = "\n".join(formatted)
248
  else:
249
+ transcript = full_text.strip()
250
 
251
+ success = send_to_telegram(f"**Task {task_id} Complete!**\n\nTranscript:\n{transcript}")
252
+ if not success:
253
+ print(f"Failed to send task {task_id} to Telegram.")
254
 
255
  except Exception as e:
256
+ send_to_telegram(f"Task {task_id} failed: {str(e)}")
257
+
258
+ # Starter function for uploaded file
259
+ def start_transcribe_upload(audio_input, model_id, language, timestamps_checkbox):
260
+ task_id = str(uuid.uuid4())[:8]
261
+ with concurrent.futures.ThreadPoolExecutor() as executor:
262
+ executor.submit(background_transcribe, task_id, audio_input, model_id, language, timestamps_checkbox, None, None)
263
+
264
+ return f"Task {task_id} started! Transcript will be sent to your Telegram bot when complete. You can close the browser."
265
+
266
+ # Starter for podcast
267
+ def start_transcribe_podcast(podcast_input, model_id, language, timestamps_checkbox):
268
+ task_id = str(uuid.uuid4())[:8]
269
+ with concurrent.futures.ThreadPoolExecutor() as executor:
270
+ executor.submit(background_transcribe, task_id, None, model_id, language, timestamps_checkbox, podcast_input, None)
271
+
272
+ return f"Task {task_id} started! Transcript will be sent to your Telegram bot when complete. You can close the browser."
273
+
274
+ # Starter for Drive
275
+ def start_transcribe_drive(drive_input, model_id, language, timestamps_checkbox):
276
+ task_id = str(uuid.uuid4())[:8]
277
+ with concurrent.futures.ThreadPoolExecutor() as executor:
278
+ executor.submit(background_transcribe, task_id, None, model_id, language, timestamps_checkbox, None, drive_input)
279
+
280
+ return f"Task {task_id} started! Transcript will be sent to your Telegram bot when complete. You can close the browser."
281
 
282
  # Create the Gradio app with a colorful, responsive theme
283
  theme = gr.themes.Soft(
 
291
  gr.Markdown(
292
  """
293
  # 🎤 MP3 to Text Transcription Tool
294
+ Upload an MP3, paste an Apple Podcasts URL, or provide a Google Drive shareable link to transcribe asynchronously.
295
+ Results are sent to your Telegram bot—no need to wait in the browser!
296
+ (Bot token and chat ID are set as secrets in HF Space settings.)
297
  """,
298
  elem_classes=["centered"]
299
  )
300
 
301
  with gr.Row(variant="panel", elem_classes=["max-w-4xl mx-auto"]):
302
  with gr.Column(scale=1):
303
+ # Inputs (no Telegram fields anymore)
304
  audio_input = gr.Audio(
305
  sources="upload",
306
  type="filepath",
 
308
  elem_classes=["w-full"]
309
  )
310
 
 
311
  podcast_input = gr.Textbox(
312
  label="🔗 Apple Podcasts Episode URL (optional)",
313
  placeholder="e.g., https://podcasts.apple.com/us/podcast/.../id123?i=456",
314
  elem_classes=["w-full"]
315
  )
316
 
317
+ drive_input = gr.Textbox(
318
+ label="📂 Google Drive Shareable Link (optional)",
319
+ placeholder="e.g., https://drive.google.com/file/d/ABC123/view?usp=sharing",
320
+ elem_classes=["w-full"]
321
+ )
322
+
323
  model_dropdown = gr.Dropdown(
324
  choices=MODEL_OPTIONS,
325
  value=MODEL_OPTIONS[1],
 
343
  )
344
 
345
  with gr.Column(scale=1):
346
+ status_output = gr.Markdown("Ready to start task! 💬", elem_classes=["text-center"])
347
 
348
  # Buttons
349
  with gr.Row(elem_classes=["w-full"]):
350
+ transcribe_btn = gr.Button("🚀 Start Transcribe Upload", variant="secondary", elem_classes=["flex-1"])
351
+ podcast_btn = gr.Button("📡 Start Podcast Transcribe", variant="primary", elem_classes=["flex-1"])
352
+ drive_btn = gr.Button("📂 Start Drive Transcribe", variant="primary", elem_classes=["flex-1"])
353
 
354
+ # Events (removed Telegram inputs)
 
 
 
 
 
 
 
 
 
355
  transcribe_btn.click(
356
+ fn=start_transcribe_upload,
357
+ inputs=[audio_input, model_dropdown, language_dropdown, timestamps_checkbox],
358
+ outputs=status_output
 
359
  )
360
 
 
361
  podcast_btn.click(
362
+ fn=start_transcribe_podcast,
363
+ inputs=[podcast_input, model_dropdown, language_dropdown, timestamps_checkbox],
364
+ outputs=status_output
365
+ )
366
+
367
+ drive_btn.click(
368
+ fn=start_transcribe_drive,
369
+ inputs=[drive_input, model_dropdown, language_dropdown, timestamps_checkbox],
370
+ outputs=status_output
371
  )
372
 
373
  if __name__ == "__main__":