Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
@@ -3,30 +3,33 @@
|
|
3 |
|
4 |
import gradio as gr
|
5 |
import openai
|
6 |
-
import os
|
7 |
-
import re
|
8 |
from pydub import AudioSegment
|
9 |
import uuid
|
10 |
import edge_tts
|
11 |
import json
|
|
|
|
|
|
|
12 |
|
13 |
def create_client(api_key=None):
|
14 |
if api_key:
|
15 |
openai.api_key = api_key
|
16 |
else:
|
17 |
-
openai.api_key = os.getenv("
|
18 |
return openai.OpenAI(api_key=openai.api_key, base_url="https://api.sambanova.ai/v1")
|
19 |
|
20 |
def generate_response(input_text, language, speaker1, speaker2, api_key):
|
21 |
speaker1_name = speaker1.split(' - ')[0]
|
22 |
speaker2_name = speaker2.split(' - ')[0]
|
|
|
|
|
23 |
if language == "Auto Detect":
|
24 |
language_instruction = "- The podcast MUST be in the same language as the user input."
|
25 |
else:
|
26 |
language_instruction = f"- The podcast Must reply to me in {language} language."
|
27 |
example = """
|
28 |
{
|
29 |
-
"topic": "
|
30 |
"podcast": [
|
31 |
{
|
32 |
"speaker": 1,
|
@@ -53,23 +56,23 @@ def generate_response(input_text, language, speaker1, speaker2, api_key):
|
|
53 |
}
|
54 |
"""
|
55 |
|
56 |
-
system_prompt = f"""
|
57 |
|
58 |
以下是你將要處理的輸入文字:
|
59 |
<input_text>
|
60 |
{{input_text}}
|
61 |
</input_text>
|
62 |
|
63 |
-
|
64 |
|
65 |
<scratchpad>
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
</scratchpad>
|
71 |
|
72 |
-
|
73 |
{language_instruction}
|
74 |
- The podcast should have 2 speakers: {speaker1_name} and {speaker2_name}.
|
75 |
- The podcast should be long.
|
@@ -79,9 +82,9 @@ Follow this JSON example structure, MUST be in {language} language:
|
|
79 |
{example}
|
80 |
|
81 |
<podcast_dialogue>
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
</podcast_dialogue>
|
86 |
"""
|
87 |
client = create_client(api_key)
|
@@ -102,28 +105,30 @@ Follow this JSON example structure, MUST be in {language} language:
|
|
102 |
except json.JSONDecodeError:
|
103 |
podcast_json = re.sub(r',\s*}', '}', podcast_json)
|
104 |
podcast_json = re.sub(r',\s*]', ']', podcast_json)
|
|
|
|
|
105 |
return podcast_json
|
106 |
else:
|
107 |
-
raise gr.Error("生成 Podcast
|
108 |
except Exception as e:
|
109 |
if "API key not valid" in str(e):
|
110 |
raise gr.Error("無效的 API 金鑰!!請提供有效的 API 金鑰。")
|
111 |
elif "rate limit" in str(e).lower():
|
112 |
raise gr.Error("API 金鑰使用額度已超過限制!!請稍後再試或使用其他 API 金鑰。")
|
113 |
else:
|
114 |
-
raise gr.Error(f"生成 Podcast
|
115 |
|
116 |
async def tts_generate(input_text, speaker1, speaker2):
|
117 |
voice_names = {
|
118 |
"家豪 - 中文 (Male)": "zh-TW-YunJheNeural",
|
119 |
"淑芬 - 中文 (Female)": "zh-TW-HsiaoChenNeural",
|
120 |
"子晴 - 中文 (Female)": "zh-TW-HsiaoYuNeural",
|
|
|
121 |
"品妍 - 中文 (Female)": "zh-CN-XiaoxiaoNeural",
|
|
|
122 |
"美玲 - 中文 (Female)": "zh-CN-XiaoyiNeural",
|
123 |
"建宏 - 中文 (Male)": "zh-CN-YunjianNeural",
|
124 |
-
"品睿 - 中文 (Male)": "zh-CN-YunxiNeural",
|
125 |
"宥廷 - 中文 (Male)": "zh-CN-YunxiaNeural",
|
126 |
-
"志明 - 中文 (Male)": "zh-CN-YunyangNeural",
|
127 |
"雨霏 - 中文 (Female)": "zh-CN-liaoning-XiaobeiNeural",
|
128 |
"Andrew - English (Male)": "en-US-AndrewMultilingualNeural",
|
129 |
"Ava - English (Female)": "en-US-AvaMultilingualNeural",
|
@@ -137,6 +142,8 @@ async def tts_generate(input_text, speaker1, speaker2):
|
|
137 |
|
138 |
speaker1_voice = voice_names[speaker1]
|
139 |
speaker2_voice = voice_names[speaker2]
|
|
|
|
|
140 |
|
141 |
try:
|
142 |
podcast_dict = json.loads(input_text)
|
@@ -176,9 +183,13 @@ async def tts_generate(input_text, speaker1, speaker2):
|
|
176 |
|
177 |
output_file = f"Jiangxz_{uuid.uuid4()}.mp3"
|
178 |
combined.export(output_file, format="mp3")
|
|
|
|
|
179 |
return output_file
|
180 |
|
181 |
async def process_podcast(input_text, language, speaker1, speaker2, api_key):
|
|
|
|
|
182 |
podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
|
183 |
speaker1_name = speaker1.split(' - ')[0]
|
184 |
speaker2_name = speaker2.split(' - ')[0]
|
@@ -196,6 +207,9 @@ async def process_podcast(input_text, language, speaker1, speaker2, api_key):
|
|
196 |
podcast_text = "Error: Unable to parse the podcast script."
|
197 |
|
198 |
audio_file = await tts_generate(podcast_script, speaker1, speaker2)
|
|
|
|
|
|
|
199 |
return podcast_text, audio_file
|
200 |
|
201 |
custom_css = """
|
@@ -287,20 +301,22 @@ body {
|
|
287 |
|
288 |
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
289 |
gr.Markdown("""
|
290 |
-
# 🎙️
|
291 |
-
> ### **※
|
292 |
""", elem_classes="center-aligned")
|
293 |
|
294 |
input_text = gr.Textbox(
|
295 |
label="請輸入 Podcast 話題(建議50至1000字)",
|
296 |
-
placeholder="輸入 Podcast
|
297 |
elem_classes="input-background",
|
298 |
max_lines=20
|
299 |
)
|
300 |
|
301 |
def check_input_length(text):
|
302 |
-
if len(text)
|
303 |
-
return gr.Warning("
|
|
|
|
|
304 |
|
305 |
input_text.change(fn=check_input_length, inputs=[input_text])
|
306 |
|
@@ -318,7 +334,7 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
|
318 |
"家豪 - 中文 (Male)",
|
319 |
"淑芬 - 中文 (Female)",
|
320 |
"子晴 - 中文 (Female)",
|
321 |
-
"
|
322 |
"品妍 - 中文 (Female)",
|
323 |
"志明 - 中文 (Male)",
|
324 |
"美玲 - 中文 (Female)",
|
@@ -337,7 +353,7 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
|
337 |
|
338 |
Speaker_1 = gr.Dropdown(
|
339 |
choices=speaker_choices,
|
340 |
-
value="
|
341 |
label="播客#1語音",
|
342 |
interactive=True,
|
343 |
scale=2,
|
@@ -360,11 +376,12 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
|
360 |
api_key = gr.Textbox(label="請輸入您的 API Key", type="password", placeholder="API authentication key for large language models", scale=1, elem_classes="api-background")
|
361 |
|
362 |
audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
|
363 |
-
podcast_script = gr.Textbox(label="Generated Podcast
|
364 |
generate_button.click(fn=process_podcast, inputs=[input_text, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
|
365 |
|
|
|
366 |
if __name__ == "__main__":
|
367 |
if "SPACE_ID" in os.environ:
|
368 |
iface.launch()
|
369 |
else:
|
370 |
-
iface.launch(share=True, show_api=False)
|
|
|
3 |
|
4 |
import gradio as gr
|
5 |
import openai
|
|
|
|
|
6 |
from pydub import AudioSegment
|
7 |
import uuid
|
8 |
import edge_tts
|
9 |
import json
|
10 |
+
import os
|
11 |
+
import re
|
12 |
+
import time
|
13 |
|
14 |
def create_client(api_key=None):
|
15 |
if api_key:
|
16 |
openai.api_key = api_key
|
17 |
else:
|
18 |
+
openai.api_key = os.getenv("YOUR_API_KEY")
|
19 |
return openai.OpenAI(api_key=openai.api_key, base_url="https://api.sambanova.ai/v1")
|
20 |
|
21 |
def generate_response(input_text, language, speaker1, speaker2, api_key):
|
22 |
speaker1_name = speaker1.split(' - ')[0]
|
23 |
speaker2_name = speaker2.split(' - ')[0]
|
24 |
+
gr.Info("正在生成 Podcast 文稿中,請稍待片刻......")
|
25 |
+
start_time = time.time()
|
26 |
if language == "Auto Detect":
|
27 |
language_instruction = "- The podcast MUST be in the same language as the user input."
|
28 |
else:
|
29 |
language_instruction = f"- The podcast Must reply to me in {language} language."
|
30 |
example = """
|
31 |
{
|
32 |
+
"topic": "AIF",
|
33 |
"podcast": [
|
34 |
{
|
35 |
"speaker": 1,
|
|
|
56 |
}
|
57 |
"""
|
58 |
|
59 |
+
system_prompt = f"""你的任務是將提供的輸入文字轉換為一個引人入勝、訊息豐富且專業的播客對話。輸入文字可能會比較混亂或結構不完整,因為它可能來自不同來源,如PDF檔案或網頁文字等。不要擔心格式問題或任何不相關的訊息;你的目標是提取可以在播客中討論的關鍵點、識別重要定義,並突顯有趣的事實。
|
60 |
|
61 |
以下是你將要處理的輸入文字:
|
62 |
<input_text>
|
63 |
{{input_text}}
|
64 |
</input_text>
|
65 |
|
66 |
+
首先,仔細閱讀輸入文字,並積極找出主要話題、關鍵點,以及任何有趣的事實或軼事。Chain-of-Thought思考如何將這些訊息以一種有趣且吸引人的方式呈現出來,適合高品質的播客文稿。
|
67 |
|
68 |
<scratchpad>
|
69 |
+
頭腦風暴一些創造性的方法來討論你在輸入文字中識別出的主要話題、關鍵點及任何有趣的事實或軼事。可以考慮使用類比、例子、講故事技巧或假設情境來讓內容更能與聽眾產生共鳴並吸引他們。
|
70 |
+
請記住,你的播客應當易於普通聽眾理解,所以避免使用過多的專業術語或假設聽眾對該話題已有瞭解。如有必要,請思考如何用簡單的術語簡要解釋任何複雜的概念。
|
71 |
+
發揮你的想像力填補輸入文字中的任何空白,或頭腦風暴提出一些值得探討與發人深省的問題,以供播客討論。目標是創造一個訊息豐富且娛樂性強的對話,因此可以在你的方法上大膽盡情自由發揮創意。
|
72 |
+
將你的頭腦風暴想法和播客對話的粗略大綱寫在這裡,務必讓它有趣且吸引人。確保記錄下你希望在結尾重申的主要見解和要點。
|
73 |
</scratchpad>
|
74 |
|
75 |
+
現在你已經進行頭腦風暴並建立粗略大綱,是時候撰寫實際的播客對話了。目標是主持人({speaker1_name})與嘉賓({speaker2_name})之間自然、對話式的交流,融入你在頭腦風暴中得出的最佳想法,並花費精力確保將任何複雜話題以易於理解的方式解釋清楚。
|
76 |
{language_instruction}
|
77 |
- The podcast should have 2 speakers: {speaker1_name} and {speaker2_name}.
|
78 |
- The podcast should be long.
|
|
|
82 |
{example}
|
83 |
|
84 |
<podcast_dialogue>
|
85 |
+
根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫一段引人入勝且訊息豐富的播客對話。採用對話式的語氣,並包括任何必要的上下文或解釋,使內容對一般聽眾容易理解。使用主持人名字 {speaker1_name} 和嘉賓名字 {speaker2_name},為聽眾營造更吸引人和身臨其境的聆聽體驗。不要包括像[主持人]或[嘉賓]這樣的括號預留位置。設計你的輸出內容必須適合直接朗讀,因為它將直接轉換為音訊。
|
86 |
+
確保對話儘可能詳細且完整,同時保持在主題之內並維持吸引人的流暢性。目標是使用你的全部輸出容量,建立儘可能長的播客節目,同時以娛樂性的方式傳達輸入文字中的關鍵訊息。
|
87 |
+
在對話結束時,讓主持人和嘉賓自然總結他們討論中的主要見解和要點,這應當是對話的隨機部分,以自然隨意而非明顯的總結 - 目的是在結束前最後一次以自然流暢的方式強化核心思想。最終以感謝詞結束。
|
88 |
</podcast_dialogue>
|
89 |
"""
|
90 |
client = create_client(api_key)
|
|
|
105 |
except json.JSONDecodeError:
|
106 |
podcast_json = re.sub(r',\s*}', '}', podcast_json)
|
107 |
podcast_json = re.sub(r',\s*]', ']', podcast_json)
|
108 |
+
end_time = time.time()
|
109 |
+
gr.Info(f"已成功生成 Podcast 文稿,執行時間: {(end_time - start_time):.2f} 秒。")
|
110 |
return podcast_json
|
111 |
else:
|
112 |
+
raise gr.Error("生成 Podcast 文稿失敗!!請稍後再試。")
|
113 |
except Exception as e:
|
114 |
if "API key not valid" in str(e):
|
115 |
raise gr.Error("無效的 API 金鑰!!請提供有效的 API 金鑰。")
|
116 |
elif "rate limit" in str(e).lower():
|
117 |
raise gr.Error("API 金鑰使用額度已超過限制!!請稍後再試或使用其他 API 金鑰。")
|
118 |
else:
|
119 |
+
raise gr.Error(f"生成 Podcast 文稿失敗!!請稍後再試。")
|
120 |
|
121 |
async def tts_generate(input_text, speaker1, speaker2):
|
122 |
voice_names = {
|
123 |
"家豪 - 中文 (Male)": "zh-TW-YunJheNeural",
|
124 |
"淑芬 - 中文 (Female)": "zh-TW-HsiaoChenNeural",
|
125 |
"子晴 - 中文 (Female)": "zh-TW-HsiaoYuNeural",
|
126 |
+
"景睿 - 中文 (Male)": "zh-CN-YunxiNeural",
|
127 |
"品妍 - 中文 (Female)": "zh-CN-XiaoxiaoNeural",
|
128 |
+
"志明 - 中文 (Male)": "zh-CN-YunyangNeural",
|
129 |
"美玲 - 中文 (Female)": "zh-CN-XiaoyiNeural",
|
130 |
"建宏 - 中文 (Male)": "zh-CN-YunjianNeural",
|
|
|
131 |
"宥廷 - 中文 (Male)": "zh-CN-YunxiaNeural",
|
|
|
132 |
"雨霏 - 中文 (Female)": "zh-CN-liaoning-XiaobeiNeural",
|
133 |
"Andrew - English (Male)": "en-US-AndrewMultilingualNeural",
|
134 |
"Ava - English (Female)": "en-US-AvaMultilingualNeural",
|
|
|
142 |
|
143 |
speaker1_voice = voice_names[speaker1]
|
144 |
speaker2_voice = voice_names[speaker2]
|
145 |
+
gr.Info("正在生成 Podcast 音檔中,請稍待片刻......")
|
146 |
+
start_time = time.time()
|
147 |
|
148 |
try:
|
149 |
podcast_dict = json.loads(input_text)
|
|
|
183 |
|
184 |
output_file = f"Jiangxz_{uuid.uuid4()}.mp3"
|
185 |
combined.export(output_file, format="mp3")
|
186 |
+
end_time = time.time()
|
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]
|
|
|
207 |
podcast_text = "Error: Unable to parse the podcast script."
|
208 |
|
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 = """
|
|
|
301 |
|
302 |
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
303 |
gr.Markdown("""
|
304 |
+
# 🎙️ 聲音經濟 - 財資歐北共 Podcast 🎙️
|
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 |
|
|
|
334 |
"家豪 - 中文 (Male)",
|
335 |
"淑芬 - 中文 (Female)",
|
336 |
"子晴 - 中文 (Female)",
|
337 |
+
"景睿 - 中文 (Male)",
|
338 |
"品妍 - 中文 (Female)",
|
339 |
"志明 - 中文 (Male)",
|
340 |
"美玲 - 中文 (Female)",
|
|
|
353 |
|
354 |
Speaker_1 = gr.Dropdown(
|
355 |
choices=speaker_choices,
|
356 |
+
value="景睿 - 中文 (Male)",
|
357 |
label="播客#1語音",
|
358 |
interactive=True,
|
359 |
scale=2,
|
|
|
376 |
api_key = gr.Textbox(label="請輸入您的 API Key", type="password", placeholder="API authentication key for large language models", scale=1, elem_classes="api-background")
|
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__":
|
384 |
if "SPACE_ID" in os.environ:
|
385 |
iface.launch()
|
386 |
else:
|
387 |
+
iface.launch(share=True, show_api=False)
|