konieshadow commited on
Commit
adb928d
·
1 Parent(s): 5825f39

在应用程序中添加会话级别的临时文件管理功能,优化临时文件清理逻辑,并引入系统资源检查以提高稳定性和用户体验。同时更新依赖项以支持新功能。

Browse files
requirements.txt CHANGED
@@ -9,6 +9,7 @@ feedparser>=6.0.11
9
  requests>=2.32.3
10
  gradio>=5.30.0
11
  spaces>=0.36.0
 
12
 
13
  # 可选依赖 - whisper.cpp 绑定
14
  pywhispercpp>=1.3.0
 
9
  requests>=2.32.3
10
  gradio>=5.30.0
11
  spaces>=0.36.0
12
+ psutil>=5.9.0
13
 
14
  # 可选依赖 - whisper.cpp 绑定
15
  pywhispercpp>=1.3.0
src/podcast_transcribe/webui/app.py CHANGED
@@ -8,6 +8,9 @@ import os
8
  import uuid
9
  import atexit
10
  import shutil
 
 
 
11
 
12
  # 尝试相对导入,这在通过 `python -m src.podcast_transcribe.webui.app` 运行时有效
13
  try:
@@ -33,28 +36,129 @@ except ImportError:
33
  from podcast_transcribe.schemas import PodcastChannel, PodcastEpisode, CombinedTranscriptionResult, EnhancedSegment
34
  from podcast_transcribe.transcriber import transcribe_podcast_audio
35
 
36
- # 用于存储应用程序使用的所有临时文件路径
37
- temp_files = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  def cleanup_temp_files():
40
  """清理应用程序使用的临时文件"""
41
- global temp_files
42
- print(f"应用程序退出,清理 {len(temp_files)} 个临时文件...")
43
-
44
- for filepath in temp_files:
45
- try:
46
- if os.path.exists(filepath):
47
- os.remove(filepath)
48
- print(f"已删除临时文件: {filepath}")
49
- except Exception as e:
50
- print(f"无法删除临时文件 {filepath}: {e}")
51
-
52
- # 清空列表
53
- temp_files = []
54
 
55
  # 注册应用程序退出时的清理函数
56
  atexit.register(cleanup_temp_files)
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def parse_rss_feed(rss_url: str):
59
  """回调函数:解析 RSS Feed"""
60
  print(f"开始解析RSS: {rss_url}")
@@ -162,11 +266,27 @@ def parse_rss_feed(rss_url: str):
162
  selected_episode_index_state: None
163
  }
164
 
165
- def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel):
166
  """回调函数:当用户从下拉菜单选择一个剧集时加载音频"""
167
- global temp_files
168
  print(f"开始加载剧集音频,选择的索引: {selected_episode_index}")
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  if selected_episode_index is None or podcast_data is None or not podcast_data.episodes:
171
  print("未选择剧集或无播客数据")
172
  return {
@@ -235,12 +355,29 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
235
 
236
  # 创建临时文件
237
  temp_dir = tempfile.gettempdir()
238
- # 使用UUID生成唯一文件名,避免冲突
239
- unique_filename = f"podcast_audio_{uuid.uuid4().hex}"
240
 
241
- # 先发送一个HEAD请求获取内容类型
242
  head_response = requests.head(audio_url, timeout=30, headers=headers)
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  # 根据内容类型确定文件扩展名
245
  content_type = head_response.headers.get('Content-Type', '').lower()
246
  if 'mp3' in content_type:
@@ -259,8 +396,8 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
259
 
260
  temp_filepath = os.path.join(temp_dir, unique_filename + file_ext)
261
 
262
- # 将文件路径添加到全局临时文件列表
263
- temp_files.append(temp_filepath)
264
 
265
  # 保存到临时文件
266
  # 使用流式下载,避免一次性加载整个文件到内存
@@ -289,7 +426,7 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
289
  return {
290
  audio_player: gr.update(value=temp_filepath, label=f"Now Playing: {episode.title or 'Untitled'}"),
291
  current_audio_url_state: audio_url,
292
- status_message_area: gr.update(value=f"Episode loaded: {episode.title or 'Untitled'}."),
293
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
294
  transcription_output_df: gr.update(value=None),
295
  local_audio_file_path: temp_filepath,
@@ -302,7 +439,7 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
302
  return {
303
  audio_player: gr.update(value=None),
304
  current_audio_url_state: None,
305
- status_message_area: gr.update(value=f"Error: Failed to download audio: {e}"),
306
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
307
  transcription_output_df: gr.update(value=None),
308
  local_audio_file_path: None,
@@ -314,7 +451,7 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
314
  return {
315
  audio_player: gr.update(value=None),
316
  current_audio_url_state: None,
317
- status_message_area: gr.update(value=f"Error: Selected episode '{episode.title}' does not provide a valid audio URL."),
318
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
319
  transcription_output_df: gr.update(value=None),
320
  local_audio_file_path: None,
@@ -326,7 +463,7 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
326
  return {
327
  audio_player: gr.update(value=None),
328
  current_audio_url_state: None,
329
- status_message_area: gr.update(value="Error: Invalid episode index selected."),
330
  episode_shownotes: gr.update(value="", visible=False),
331
  transcription_output_df: gr.update(value=None),
332
  local_audio_file_path: None,
@@ -339,7 +476,7 @@ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel
339
  return {
340
  audio_player: gr.update(value=None),
341
  current_audio_url_state: None,
342
- status_message_area: gr.update(value=f"Serious error occurred while loading audio: {e}"),
343
  episode_shownotes: gr.update(value="", visible=False),
344
  transcription_output_df: gr.update(value=None),
345
  local_audio_file_path: None,
@@ -361,11 +498,22 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
361
  """回调函数:开始转录当前加载的音频"""
362
  print(f"开始转录本地音频文件: {local_audio_file_path}, 选中剧集索引: {selected_episode_index}")
363
 
 
 
 
 
 
 
 
 
 
 
 
364
  if not local_audio_file_path or not os.path.exists(local_audio_file_path):
365
  print("没有可用的本地音频文件")
366
  return {
367
  transcription_output_df: gr.update(value=None),
368
- status_message_area: gr.update(value="Error: No valid audio file for transcription. Please select an episode first."),
369
  parse_button: gr.update(interactive=True),
370
  episode_dropdown: gr.update(interactive=True),
371
  transcribe_button: gr.update(interactive=True)
@@ -380,7 +528,19 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
380
 
381
  # 从文件加载音频
382
  audio_segment = AudioSegment.from_file(local_audio_file_path)
383
- print(f"音频加载完成,时长: {len(audio_segment)/1000}秒")
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
  progress(0.4, desc="Audio loaded, starting transcription (this may take a while)...")
386
 
@@ -396,7 +556,7 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
396
  result: CombinedTranscriptionResult = transcribe_podcast_audio(audio_segment,
397
  podcast_info=podcast_data,
398
  episode_info=episode_info,
399
- segmentation_batch_size=64,
400
  parallel=True)
401
  print(f"转录完成,结果: {result is not None}, 段落数: {len(result.segments) if result and result.segments else 0}")
402
  progress(0.9, desc="Transcription completed, formatting results...")
@@ -410,7 +570,7 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
410
  progress(1.0, desc="Transcription results generated!")
411
  return {
412
  transcription_output_df: gr.update(value=formatted_segments),
413
- status_message_area: gr.update(value=f"Transcription completed! {len(result.segments)} segments generated. {result.num_speakers} speakers detected."),
414
  parse_button: gr.update(interactive=True),
415
  episode_dropdown: gr.update(interactive=True),
416
  transcribe_button: gr.update(interactive=True)
@@ -419,7 +579,7 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
419
  progress(1.0, desc="Transcription completed, but no text segments")
420
  return {
421
  transcription_output_df: gr.update(value=None),
422
- status_message_area: gr.update(value="Transcription completed, but no text segments were generated."),
423
  parse_button: gr.update(interactive=True),
424
  episode_dropdown: gr.update(interactive=True),
425
  transcribe_button: gr.update(interactive=True)
@@ -428,7 +588,7 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
428
  progress(1.0, desc="Transcription failed")
429
  return {
430
  transcription_output_df: gr.update(value=None),
431
- status_message_area: gr.update(value="Transcription failed, no results obtained."),
432
  parse_button: gr.update(interactive=True),
433
  episode_dropdown: gr.update(interactive=True),
434
  transcribe_button: gr.update(interactive=True)
@@ -439,7 +599,7 @@ def start_transcription(local_audio_file_path: str, podcast_data: PodcastChannel
439
  progress(1.0, desc="Transcription failed: processing error")
440
  return {
441
  transcription_output_df: gr.update(value=None),
442
- status_message_area: gr.update(value=f"Serious error occurred during transcription: {e}"),
443
  parse_button: gr.update(interactive=True),
444
  episode_dropdown: gr.update(interactive=True),
445
  transcribe_button: gr.update(interactive=True)
@@ -467,6 +627,14 @@ with gr.Blocks(title="Podcast Transcriber v2", css="""
467
  border-radius: 8px;
468
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
469
  }
 
 
 
 
 
 
 
 
470
  """) as demo:
471
  gr.Markdown("# 🎙️ Podcast Transcriber")
472
 
 
8
  import uuid
9
  import atexit
10
  import shutil
11
+ import threading
12
+ import time
13
+ from typing import Dict, Set
14
 
15
  # 尝试相对导入,这在通过 `python -m src.podcast_transcribe.webui.app` 运行时有效
16
  try:
 
36
  from podcast_transcribe.schemas import PodcastChannel, PodcastEpisode, CombinedTranscriptionResult, EnhancedSegment
37
  from podcast_transcribe.transcriber import transcribe_podcast_audio
38
 
39
+ # 会话级别的临时文件管理
40
+ class SessionFileManager:
41
+ def __init__(self):
42
+ self.session_files: Dict[str, Set[str]] = {}
43
+ self.lock = threading.Lock()
44
+ self.cleanup_thread = None
45
+ self.start_cleanup_thread()
46
+
47
+ def start_cleanup_thread(self):
48
+ """启动后台清理线程"""
49
+ if self.cleanup_thread is None or not self.cleanup_thread.is_alive():
50
+ self.cleanup_thread = threading.Thread(target=self._periodic_cleanup, daemon=True)
51
+ self.cleanup_thread.start()
52
+
53
+ def _periodic_cleanup(self):
54
+ """定期清理过期的临时文件"""
55
+ while True:
56
+ try:
57
+ time.sleep(300) # 每5分钟清理一次
58
+ self._cleanup_old_files()
59
+ except Exception as e:
60
+ print(f"清理线程错误: {e}")
61
+
62
+ def _cleanup_old_files(self):
63
+ """清理超过30分钟的临时文件"""
64
+ current_time = time.time()
65
+ with self.lock:
66
+ for session_id, files in list(self.session_files.items()):
67
+ files_to_remove = []
68
+ for filepath in list(files):
69
+ try:
70
+ if os.path.exists(filepath):
71
+ # 检查文件创建时间
72
+ file_age = current_time - os.path.getctime(filepath)
73
+ if file_age > 1800: # 30分钟
74
+ os.remove(filepath)
75
+ files_to_remove.append(filepath)
76
+ print(f"自动清理过期临时文件: {filepath}")
77
+ else:
78
+ files_to_remove.append(filepath)
79
+ except Exception as e:
80
+ print(f"清理文件 {filepath} 时出错: {e}")
81
+ files_to_remove.append(filepath)
82
+
83
+ # 从集合中移除已清理的文件
84
+ for filepath in files_to_remove:
85
+ files.discard(filepath)
86
+
87
+ # 如果会话没有文件了,移除会话记录
88
+ if not files:
89
+ del self.session_files[session_id]
90
+
91
+ def add_file(self, session_id: str, filepath: str):
92
+ """添加文件到会话管理"""
93
+ with self.lock:
94
+ if session_id not in self.session_files:
95
+ self.session_files[session_id] = set()
96
+ self.session_files[session_id].add(filepath)
97
+
98
+ def cleanup_session(self, session_id: str):
99
+ """清理特定会话的所有文件"""
100
+ with self.lock:
101
+ if session_id in self.session_files:
102
+ files = self.session_files[session_id]
103
+ for filepath in list(files):
104
+ try:
105
+ if os.path.exists(filepath):
106
+ os.remove(filepath)
107
+ print(f"清理会话文件: {filepath}")
108
+ except Exception as e:
109
+ print(f"无法删除文件 {filepath}: {e}")
110
+ del self.session_files[session_id]
111
+
112
+ def cleanup_all(self):
113
+ """清理所有临时文件"""
114
+ with self.lock:
115
+ total_files = 0
116
+ for session_id in list(self.session_files.keys()):
117
+ total_files += len(self.session_files[session_id])
118
+ self.cleanup_session(session_id)
119
+ print(f"应用程序退出,清理了 {total_files} 个临时文件")
120
+
121
+ # 全局文件管理器
122
+ file_manager = SessionFileManager()
123
 
124
  def cleanup_temp_files():
125
  """清理应用程序使用的临时文件"""
126
+ file_manager.cleanup_all()
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  # 注册应用程序退出时的清理函数
129
  atexit.register(cleanup_temp_files)
130
 
131
+ def get_session_id(request: gr.Request = None) -> str:
132
+ """获取会话ID,用于文件管理"""
133
+ if request and hasattr(request, 'session_hash'):
134
+ return request.session_hash
135
+ else:
136
+ # 如果无法获取会话ID,使用UUID
137
+ return str(uuid.uuid4())
138
+
139
+ # 添加资源限制检查
140
+ def check_system_resources():
141
+ """检查系统资源是否足够"""
142
+ try:
143
+ import psutil
144
+ # 检查可用内存
145
+ memory = psutil.virtual_memory()
146
+ if memory.available < 500 * 1024 * 1024: # 少于500MB
147
+ return False, "系统内存不足,请稍后再试"
148
+
149
+ # 检查磁盘空间
150
+ disk = psutil.disk_usage(tempfile.gettempdir())
151
+ if disk.free < 1024 * 1024 * 1024: # 少于1GB
152
+ return False, "磁盘空间不足,请稍后再试"
153
+
154
+ return True, "资源充足"
155
+ except ImportError:
156
+ # 如果没有psutil,跳过检查
157
+ return True, "无法检查资源状态"
158
+ except Exception as e:
159
+ print(f"资源检查错误: {e}")
160
+ return True, "资源检查失败,继续执行"
161
+
162
  def parse_rss_feed(rss_url: str):
163
  """回调函数:解析 RSS Feed"""
164
  print(f"开始解析RSS: {rss_url}")
 
266
  selected_episode_index_state: None
267
  }
268
 
269
+ def load_episode_audio(selected_episode_index: int, podcast_data: PodcastChannel, request: gr.Request = None):
270
  """回调函数:当用户从下拉菜单选择一个剧集时加载音频"""
 
271
  print(f"开始加载剧集音频,选择的索引: {selected_episode_index}")
272
 
273
+ # 获取会话ID
274
+ session_id = get_session_id(request)
275
+
276
+ # 检查系统资源
277
+ resource_ok, resource_msg = check_system_resources()
278
+ if not resource_ok:
279
+ return {
280
+ audio_player: gr.update(value=None),
281
+ current_audio_url_state: None,
282
+ status_message_area: gr.update(value=f"⚠️ {resource_msg}"),
283
+ episode_shownotes: gr.update(value="", visible=False),
284
+ transcription_output_df: gr.update(value=None),
285
+ local_audio_file_path: None,
286
+ transcribe_button: gr.update(interactive=False),
287
+ selected_episode_index_state: None
288
+ }
289
+
290
  if selected_episode_index is None or podcast_data is None or not podcast_data.episodes:
291
  print("未选择剧集或无播客数据")
292
  return {
 
355
 
356
  # 创建临时文件
357
  temp_dir = tempfile.gettempdir()
358
+ # 使用会话ID和UUID生成唯一文件名,避免冲突
359
+ unique_filename = f"podcast_audio_{session_id[:8]}_{uuid.uuid4().hex[:8]}"
360
 
361
+ # 先发送一个HEAD请求获取内容类型和文件大小
362
  head_response = requests.head(audio_url, timeout=30, headers=headers)
363
 
364
+ # 检查文件大小限制(例如限制为200MB)
365
+ content_length = head_response.headers.get('Content-Length')
366
+ if content_length:
367
+ file_size = int(content_length)
368
+ max_size = 200 * 1024 * 1024 # 200MB
369
+ if file_size > max_size:
370
+ return {
371
+ audio_player: gr.update(value=None),
372
+ current_audio_url_state: None,
373
+ status_message_area: gr.update(value=f"⚠️ 音频文件过大 ({file_size/1024/1024:.1f}MB),超过限制 ({max_size/1024/1024}MB)"),
374
+ episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
375
+ transcription_output_df: gr.update(value=None),
376
+ local_audio_file_path: None,
377
+ transcribe_button: gr.update(interactive=False),
378
+ selected_episode_index_state: None
379
+ }
380
+
381
  # 根据内容类型确定文件扩展名
382
  content_type = head_response.headers.get('Content-Type', '').lower()
383
  if 'mp3' in content_type:
 
396
 
397
  temp_filepath = os.path.join(temp_dir, unique_filename + file_ext)
398
 
399
+ # 将文件路径添加到会话文件管理器
400
+ file_manager.add_file(session_id, temp_filepath)
401
 
402
  # 保存到临时文件
403
  # 使用流式下载,避免一次性加载整个文件到内存
 
426
  return {
427
  audio_player: gr.update(value=temp_filepath, label=f"Now Playing: {episode.title or 'Untitled'}"),
428
  current_audio_url_state: audio_url,
429
+ status_message_area: gr.update(value=f"Episode loaded: {episode.title or 'Untitled'}."),
430
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
431
  transcription_output_df: gr.update(value=None),
432
  local_audio_file_path: temp_filepath,
 
439
  return {
440
  audio_player: gr.update(value=None),
441
  current_audio_url_state: None,
442
+ status_message_area: gr.update(value=f"Error: Failed to download audio: {e}"),
443
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
444
  transcription_output_df: gr.update(value=None),
445
  local_audio_file_path: None,
 
451
  return {
452
  audio_player: gr.update(value=None),
453
  current_audio_url_state: None,
454
+ status_message_area: gr.update(value=f"Error: Selected episode '{episode.title}' does not provide a valid audio URL."),
455
  episode_shownotes: gr.update(value=episode_shownotes_content, visible=True),
456
  transcription_output_df: gr.update(value=None),
457
  local_audio_file_path: None,
 
463
  return {
464
  audio_player: gr.update(value=None),
465
  current_audio_url_state: None,
466
+ status_message_area: gr.update(value="Error: Invalid episode index selected."),
467
  episode_shownotes: gr.update(value="", visible=False),
468
  transcription_output_df: gr.update(value=None),
469
  local_audio_file_path: None,
 
476
  return {
477
  audio_player: gr.update(value=None),
478
  current_audio_url_state: None,
479
+ status_message_area: gr.update(value=f"Serious error occurred while loading audio: {e}"),
480
  episode_shownotes: gr.update(value="", visible=False),
481
  transcription_output_df: gr.update(value=None),
482
  local_audio_file_path: None,
 
498
  """回调函数:开始转录当前加载的音频"""
499
  print(f"开始转录本地音频文件: {local_audio_file_path}, 选中剧集索引: {selected_episode_index}")
500
 
501
+ # 检查系统资源
502
+ resource_ok, resource_msg = check_system_resources()
503
+ if not resource_ok:
504
+ return {
505
+ transcription_output_df: gr.update(value=None),
506
+ status_message_area: gr.update(value=f"⚠️ {resource_msg}"),
507
+ parse_button: gr.update(interactive=True),
508
+ episode_dropdown: gr.update(interactive=True),
509
+ transcribe_button: gr.update(interactive=True)
510
+ }
511
+
512
  if not local_audio_file_path or not os.path.exists(local_audio_file_path):
513
  print("没有可用的本地音频文件")
514
  return {
515
  transcription_output_df: gr.update(value=None),
516
+ status_message_area: gr.update(value="Error: No valid audio file for transcription. Please select an episode first."),
517
  parse_button: gr.update(interactive=True),
518
  episode_dropdown: gr.update(interactive=True),
519
  transcribe_button: gr.update(interactive=True)
 
528
 
529
  # 从文件加载音频
530
  audio_segment = AudioSegment.from_file(local_audio_file_path)
531
+ audio_duration = len(audio_segment) / 1000 # 转换为秒
532
+ print(f"音频加载完成,时长: {audio_duration}秒")
533
+
534
+ # 检查音频时长限制(例如限制为60分钟)
535
+ max_duration = 60 * 60 # 60分钟
536
+ if audio_duration > max_duration:
537
+ return {
538
+ transcription_output_df: gr.update(value=None),
539
+ status_message_area: gr.update(value=f"⚠️ 音频时长过长 ({audio_duration/60:.1f}分钟),超过限制 ({max_duration/60}分钟)"),
540
+ parse_button: gr.update(interactive=True),
541
+ episode_dropdown: gr.update(interactive=True),
542
+ transcribe_button: gr.update(interactive=True)
543
+ }
544
 
545
  progress(0.4, desc="Audio loaded, starting transcription (this may take a while)...")
546
 
 
556
  result: CombinedTranscriptionResult = transcribe_podcast_audio(audio_segment,
557
  podcast_info=podcast_data,
558
  episode_info=episode_info,
559
+ segmentation_batch_size=32, # 减少批次大小以节省内存
560
  parallel=True)
561
  print(f"转录完成,结果: {result is not None}, 段落数: {len(result.segments) if result and result.segments else 0}")
562
  progress(0.9, desc="Transcription completed, formatting results...")
 
570
  progress(1.0, desc="Transcription results generated!")
571
  return {
572
  transcription_output_df: gr.update(value=formatted_segments),
573
+ status_message_area: gr.update(value=f"Transcription completed! {len(result.segments)} segments generated. {result.num_speakers} speakers detected."),
574
  parse_button: gr.update(interactive=True),
575
  episode_dropdown: gr.update(interactive=True),
576
  transcribe_button: gr.update(interactive=True)
 
579
  progress(1.0, desc="Transcription completed, but no text segments")
580
  return {
581
  transcription_output_df: gr.update(value=None),
582
+ status_message_area: gr.update(value="⚠️ Transcription completed, but no text segments were generated."),
583
  parse_button: gr.update(interactive=True),
584
  episode_dropdown: gr.update(interactive=True),
585
  transcribe_button: gr.update(interactive=True)
 
588
  progress(1.0, desc="Transcription failed")
589
  return {
590
  transcription_output_df: gr.update(value=None),
591
+ status_message_area: gr.update(value="Transcription failed, no results obtained."),
592
  parse_button: gr.update(interactive=True),
593
  episode_dropdown: gr.update(interactive=True),
594
  transcribe_button: gr.update(interactive=True)
 
599
  progress(1.0, desc="Transcription failed: processing error")
600
  return {
601
  transcription_output_df: gr.update(value=None),
602
+ status_message_area: gr.update(value=f"Serious error occurred during transcription: {e}"),
603
  parse_button: gr.update(interactive=True),
604
  episode_dropdown: gr.update(interactive=True),
605
  transcribe_button: gr.update(interactive=True)
 
627
  border-radius: 8px;
628
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
629
  }
630
+ .resource-warning {
631
+ background-color: #fff3cd;
632
+ border: 1px solid #ffeaa7;
633
+ border-radius: 6px;
634
+ padding: 10px;
635
+ margin: 10px 0;
636
+ color: #856404;
637
+ }
638
  """) as demo:
639
  gr.Markdown("# 🎙️ Podcast Transcriber")
640