Spaces:
Running
Running
Upload 2 files
Browse files- app.py +61 -17
- 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("
|
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:
|
260 |
border-radius: 10px !important;
|
261 |
margin: 0 !important;
|
262 |
}
|
263 |
.sk1-background {
|
264 |
background-color: #FFF5CD !important;
|
265 |
-
padding:
|
266 |
border-radius: 10px !important;
|
267 |
margin: 0 !important;
|
268 |
}
|
269 |
.sk2-background {
|
270 |
background-color: #FFF5CD !important;
|
271 |
-
padding:
|
272 |
border-radius: 10px !important;
|
273 |
margin: 0 !important;
|
274 |
}
|
275 |
.clear-button {
|
276 |
color: black !important;
|
277 |
background-color: #FFCFB3 !important;
|
278 |
-
padding:
|
279 |
border-radius: 10px !important;
|
280 |
margin: 0 !important;
|
281 |
}
|
282 |
.api-background {
|
283 |
background-color: #FFCFB3 !important;
|
284 |
-
padding:
|
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 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
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:
|
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
|