Jiangxz01 commited on
Commit
216e871
·
verified ·
1 Parent(s): eda5fa8

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +61 -17
  2. requirements.txt +3 -1
app.py CHANGED
@@ -10,6 +10,30 @@ import json
10
  import os
11
  import re
12
  import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def create_client(api_key=None):
15
  if api_key:
@@ -84,7 +108,7 @@ Follow this JSON example structure, MUST be in {language} language:
84
  <podcast_dialogue>
85
  根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫一段引人入勝且訊息豐富的播客對話。採用對話式的語氣,並包括任何必要的上下文或解釋,使內容對一般聽眾容易理解。使用主持人名字 {speaker1_name} 和嘉賓名字 {speaker2_name},為聽眾營造更吸引人和身臨其境的聆聽體驗。不要包括像[主持人]或[嘉賓]這樣的括號預留位置。設計你的輸出內容必須適合直接朗讀,因為它將直接轉換為音訊。
86
  確保對話儘可能詳細且完整,同時保持在主題之內並維持吸引人的流暢性。目標是使用你的全部輸出容量,建立儘可能長的播客節目,同時以娛樂性的方式傳達輸入文字中的關鍵訊息。
87
- 在對話結束時,讓主持人和嘉賓自然總結他們討論中的主要見解和要點,這應當是對話的隨機部分,以自然隨意而非明顯的總結 - 目的是在結束前最後一次以自然流暢的方式強化核心思想。最終以感謝詞結束。
88
  </podcast_dialogue>
89
  """
90
  client = create_client(api_key)
@@ -187,9 +211,15 @@ async def tts_generate(input_text, speaker1, speaker2):
187
  gr.Info(f"已成功生成 Podcast 音檔,執行時間: {(end_time - start_time):.2f} 秒。")
188
  return output_file
189
 
190
- async def process_podcast(input_text, language, speaker1, speaker2, api_key):
191
  gr.Info("開始生成 Podcast 節目及音檔,請稍待片刻......")
192
  start_time = time.time()
 
 
 
 
 
 
193
  podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
194
  speaker1_name = speaker1.split(' - ')[0]
195
  speaker2_name = speaker2.split(' - ')[0]
@@ -209,7 +239,7 @@ async def process_podcast(input_text, language, speaker1, speaker2, api_key):
209
  audio_file = await tts_generate(podcast_script, speaker1, speaker2)
210
  end_time = time.time()
211
  gr.Info(f"已成功完成 Podcast 節目及音檔,總執行時間: {(end_time - start_time):.2f} 秒。")
212
- gr.Info("請待本訊息自動消失後即可播放或下載 Podcast 音檔!!")
213
  return podcast_text, audio_file
214
 
215
  custom_css = """
@@ -254,34 +284,41 @@ body {
254
  border-radius: 10px !important;
255
  margin: 0 !important;
256
  }
 
 
 
 
 
 
 
257
  .lng-background {
258
  background-color: #FFF5CD !important;
259
- padding: 5px !important;
260
  border-radius: 10px !important;
261
  margin: 0 !important;
262
  }
263
  .sk1-background {
264
  background-color: #FFF5CD !important;
265
- padding: 5px !important;
266
  border-radius: 10px !important;
267
  margin: 0 !important;
268
  }
269
  .sk2-background {
270
  background-color: #FFF5CD !important;
271
- padding: 5px !important;
272
  border-radius: 10px !important;
273
  margin: 0 !important;
274
  }
275
  .clear-button {
276
  color: black !important;
277
  background-color: #FFCFB3 !important;
278
- padding: 5px !important;
279
  border-radius: 10px !important;
280
  margin: 0 !important;
281
  }
282
  .api-background {
283
  background-color: #FFCFB3 !important;
284
- padding: 5px !important;
285
  border-radius: 10px !important;
286
  margin: 0 !important;
287
  }
@@ -305,19 +342,26 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
305
  > ### **※ 玩轉聲音魅力,開拓更多可能性,自動生成 Podcast 節目及音檔,系統布署:江信宗,LLM:Llama-3.1-405B-Instruct。**
306
  """, elem_classes="center-aligned")
307
 
308
- input_text = gr.Textbox(
309
- label="請輸入 Podcast 話題(建議50至1000字)",
310
- placeholder="輸入 Podcast 話題內容,受限 LLM Context Length,建議1000字以內 ......",
311
- elem_classes="input-background",
312
- max_lines=20
313
- )
 
 
 
 
 
 
 
 
314
 
315
  def check_input_length(text):
316
  if 0 < len(text) < 4:
317
  return gr.Warning("輸入內容過短,請提供明確的話題內容。")
318
  elif len(text) > 4096:
319
  return gr.Warning("輸入內容已超過 max tokens,請縮短話題內容。")
320
-
321
  input_text.change(fn=check_input_length, inputs=[input_text])
322
 
323
  with gr.Row():
@@ -369,7 +413,7 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
369
  )
370
 
371
  clear_input_text_button = gr.Button("清除Podcast話題", scale=1, elem_classes="clear-button")
372
- clear_input_text_button.click(fn=lambda: "", inputs=None, outputs=input_text)
373
 
374
  with gr.Row():
375
  generate_button = gr.Button("生成 Podcast 節目及音檔", scale=2, elem_classes="gen-button")
@@ -377,7 +421,7 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
377
 
378
  audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
379
  podcast_script = gr.Textbox(label="Generated Podcast 文稿", elem_classes="script-background")
380
- generate_button.click(fn=process_podcast, inputs=[input_text, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
381
 
382
 
383
  if __name__ == "__main__":
 
10
  import os
11
  import re
12
  import time
13
+ import aiofiles
14
+ import pypdf
15
+ import io
16
+
17
+ class TextExtractor:
18
+ @staticmethod
19
+ async def extract_from_pdf(file_path: str) -> str:
20
+ async with aiofiles.open(file_path, 'rb') as file:
21
+ content = await file.read()
22
+ pdf_reader = pypdf.PdfReader(io.BytesIO(content))
23
+ return "\n\n".join(page.extract_text() for page in pdf_reader.pages if page.extract_text())
24
+ @staticmethod
25
+ async def extract_from_txt(file_path: str) -> str:
26
+ async with aiofiles.open(file_path, 'r') as file:
27
+ return await file.read()
28
+ @classmethod
29
+ async def extract_text(cls, file_path: str) -> str:
30
+ _, file_extension = os.path.splitext(file_path)
31
+ if file_extension.lower() == '.pdf':
32
+ return await cls.extract_from_pdf(file_path)
33
+ elif file_extension.lower() == '.txt':
34
+ return await cls.extract_from_txt(file_path)
35
+ else:
36
+ raise gr.Error(f"Unsupported file type: {file_extension}")
37
 
38
  def create_client(api_key=None):
39
  if api_key:
 
108
  <podcast_dialogue>
109
  根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫一段引人入勝且訊息豐富的播客對話。採用對話式的語氣,並包括任何必要的上下文或解釋,使內容對一般聽眾容易理解。使用主持人名字 {speaker1_name} 和嘉賓名字 {speaker2_name},為聽眾營造更吸引人和身臨其境的聆聽體驗。不要包括像[主持人]或[嘉賓]這樣的括號預留位置。設計你的輸出內容必須適合直接朗讀,因為它將直接轉換為音訊。
110
  確保對話儘可能詳細且完整,同時保持在主題之內並維持吸引人的流暢性。目標是使用你的全部輸出容量,建立儘可能長的播客節目,同時以娛樂性的方式傳達輸入文字中的關鍵訊息。
111
+ 在對話結束時,讓主持人和嘉賓自然總結他們討論中的主要見解和要點,這應當是對話的隨機部分,以自然隨意而非明顯刻意的總結 - 目的是在結束前最後一次以自然流暢的方式強化核心思想。最終以感謝詞結束。
112
  </podcast_dialogue>
113
  """
114
  client = create_client(api_key)
 
211
  gr.Info(f"已成功生成 Podcast 音檔,執行時間: {(end_time - start_time):.2f} 秒。")
212
  return output_file
213
 
214
+ async def process_podcast(input_text, input_file, language, speaker1, speaker2, api_key):
215
  gr.Info("開始生成 Podcast 節目及音檔,請稍待片刻......")
216
  start_time = time.time()
217
+ input_text = input_text.strip()
218
+ if input_file:
219
+ input_text = await TextExtractor.extract_text(input_file.name)
220
+ if not input_text.strip():
221
+ gr.Warning("PDF檔案不得為掃描圖片檔,請您確認正確輸入文字或上傳PDF文字檔。")
222
+ return None, None
223
  podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
224
  speaker1_name = speaker1.split(' - ')[0]
225
  speaker2_name = speaker2.split(' - ')[0]
 
239
  audio_file = await tts_generate(podcast_script, speaker1, speaker2)
240
  end_time = time.time()
241
  gr.Info(f"已成功完成 Podcast 節目及音檔,總執行時間: {(end_time - start_time):.2f} 秒。")
242
+ gr.Info("請等待本訊息自動消失後即可播放或下載 Podcast 音檔!!")
243
  return podcast_text, audio_file
244
 
245
  custom_css = """
 
284
  border-radius: 10px !important;
285
  margin: 0 !important;
286
  }
287
+ .file-background {
288
+ background-color: #B7E0FF !important;
289
+ padding: 15px !important;
290
+ border-radius: 10px !important;
291
+ margin: 0 !important;
292
+ height: 135px !important;
293
+ }
294
  .lng-background {
295
  background-color: #FFF5CD !important;
296
+ padding: 10px !important;
297
  border-radius: 10px !important;
298
  margin: 0 !important;
299
  }
300
  .sk1-background {
301
  background-color: #FFF5CD !important;
302
+ padding: 10px !important;
303
  border-radius: 10px !important;
304
  margin: 0 !important;
305
  }
306
  .sk2-background {
307
  background-color: #FFF5CD !important;
308
+ padding: 10px !important;
309
  border-radius: 10px !important;
310
  margin: 0 !important;
311
  }
312
  .clear-button {
313
  color: black !important;
314
  background-color: #FFCFB3 !important;
315
+ padding: 10px !important;
316
  border-radius: 10px !important;
317
  margin: 0 !important;
318
  }
319
  .api-background {
320
  background-color: #FFCFB3 !important;
321
+ padding: 15px !important;
322
  border-radius: 10px !important;
323
  margin: 0 !important;
324
  }
 
342
  > ### **※ 玩轉聲音魅力,開拓更多可能性,自動生成 Podcast 節目及音檔,系統布署:江信宗,LLM:Llama-3.1-405B-Instruct。**
343
  """, elem_classes="center-aligned")
344
 
345
+ with gr.Row():
346
+ input_text = gr.Textbox(
347
+ label="請輸入 Podcast 話題(建議50至1000字)",
348
+ placeholder="輸入 Podcast 話題內容,受限 LLM Context Length,建議1000字以內 ......",
349
+ elem_classes="input-background",
350
+ max_lines=20,
351
+ scale=3
352
+ )
353
+ fileName = gr.File(
354
+ file_types=[".pdf", ".txt"],
355
+ label="或上傳 PDF 檔",
356
+ scale=1,
357
+ elem_classes="file-background"
358
+ )
359
 
360
  def check_input_length(text):
361
  if 0 < len(text) < 4:
362
  return gr.Warning("輸入內容過短,請提供明確的話題內容。")
363
  elif len(text) > 4096:
364
  return gr.Warning("輸入內容已超過 max tokens,請縮短話題內容。")
 
365
  input_text.change(fn=check_input_length, inputs=[input_text])
366
 
367
  with gr.Row():
 
413
  )
414
 
415
  clear_input_text_button = gr.Button("清除Podcast話題", scale=1, elem_classes="clear-button")
416
+ clear_input_text_button.click(fn=lambda: (None, None), inputs=None, outputs=[input_text, fileName])
417
 
418
  with gr.Row():
419
  generate_button = gr.Button("生成 Podcast 節目及音檔", scale=2, elem_classes="gen-button")
 
421
 
422
  audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
423
  podcast_script = gr.Textbox(label="Generated Podcast 文稿", elem_classes="script-background")
424
+ generate_button.click(fn=process_podcast, inputs=[input_text, fileName, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
425
 
426
 
427
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -1,4 +1,6 @@
1
  gradio
2
  openai
3
  pydub
4
- edge-tts
 
 
 
1
  gradio
2
  openai
3
  pydub
4
+ edge-tts
5
+ aiofiles
6
+ pypdf