Spaces:
Running
Running
File size: 15,414 Bytes
3f9fc14 8dfb6d1 71490c2 5048428 fe9bc81 5db6f6a 3f9fc14 729fc09 3e01b59 d3dfe8f bac399d 78911dc b505c58 729fc09 3f9fc14 ae24967 3f9fc14 3e01b59 729fc09 3f9fc14 71490c2 78911dc 3f9fc14 3e01b59 3f9fc14 729fc09 3f9fc14 729fc09 3f9fc14 b505c58 5ecccc7 1a2d948 5ecccc7 1a2d948 b505c58 1a2d948 b505c58 1a2d948 b505c58 1a2d948 3f9fc14 71490c2 496360c fe462a0 1a2d948 496360c 71490c2 5ecccc7 71490c2 5ecccc7 71490c2 5ecccc7 71490c2 5ecccc7 3e08982 71490c2 5ecccc7 1a2d948 71490c2 1a2d948 5ecccc7 71490c2 d40bc91 57d4798 d40bc91 57d4798 71490c2 57d4798 71490c2 b274783 9238088 04cb9bf 9238088 b274783 9238088 a6c9ed4 b274783 9238088 b274783 9238088 a6c9ed4 9238088 33d0096 62ff08b 33d0096 a6c9ed4 33d0096 62ff08b a6c9ed4 62ff08b a6c9ed4 62ff08b 33d0096 a6c9ed4 d40bc91 a6c9ed4 33d0096 fe462a0 a6c9ed4 fe462a0 91b5fb3 fe462a0 a6c9ed4 fe462a0 ec8d908 a6c9ed4 ec8d908 b274783 a6c9ed4 d40bc91 a6c9ed4 62ff08b 4db864c a6c9ed4 4db864c 62ff08b 4db864c a6c9ed4 058e306 fe462a0 1a2d948 058e306 a6c9ed4 058e306 a6c9ed4 058e306 62ff08b 058e306 a6c9ed4 058e306 62ff08b 058e306 a6c9ed4 d40bc91 a6c9ed4 62ff08b 91b5fb3 ec8d908 a6c9ed4 fe462a0 ec8d908 71490c2 3f9fc14 a6c9ed4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# -*- coding: utf-8 -*-
# 財政部財政資訊中心 江信宗
import gradio as gr
import openai
import os
import re
from pydub import AudioSegment
import uuid
import edge_tts
import json
def create_client(api_key=None):
if api_key:
openai.api_key = api_key
else:
openai.api_key = os.getenv("YOUR_API_TOKEN")
return openai.OpenAI(api_key=openai.api_key, base_url="https://api.sambanova.ai/v1")
def generate_response(input_text, language, speaker1, speaker2, api_key):
speaker1_name = speaker1.split(' - ')[0]
speaker2_name = speaker2.split(' - ')[0]
if language == "Auto Detect":
language_instruction = "- The podcast MUST be in the same language as the user input."
else:
language_instruction = f"- The podcast Must reply to me in {language} language."
example = """
{
"topic": "AGI",
"podcast": [
{
"speaker": 1,
"line": "Welcome to the 財資歐北共 Podcast. I am the host {speaker1_name}. Today we have invited an expert {speaker2_name} to join our program despite his busy schedule."
},
{
"speaker": 2,
"line": "Hello everyone, I am {speaker2_name}, I am honored to come and chat with you."
},
{
"speaker": 1,
"line": "Today we will discuss a very interesting topic..."
},
{
"speaker": 2,
"line": "Yes, this topic is indeed fascinating. Let's start with..."
},
…………,
{
"speaker": 1,
"line": "Thank you {speaker2_name} for your professional sharing. Welcome to subscribe to the Wishing Podcast. Thank you and goodbye."
}
]
}
"""
system_prompt = f"""你的任務是將提供的輸入文字轉換為一個引人入勝、訊息豐富且專業的Podcast對話。輸入文字可能會比較混亂或結構不完整,因為它可能來自不同來源,如PDF檔案或文字檔等。不要擔心格式問題或任何不相關的訊息;你的目標是提取可以在Podcast中討論的關鍵點、識別重要定義,並突出有趣的事實。
以下是你將要處理的輸入文字:
<input_text>
{{input_text}}
</input_text>
首先,仔細閱讀輸入文字,找出主要話題、關鍵點,以及任何有趣的事實或軼事。思考如何將這些訊息以一種有趣且吸引人的方式呈現出來,適合高質量的音訊Podcast。
<scratchpad>
頭腦風暴一些創造性的方法來討論你在輸入文字中識別出的主要話題、關鍵點及任何有趣的事實或軼事。可以考慮使用類比、講故事技巧或假設情境來讓內容對聽眾更加貼近和有趣。
請記住,你的Podcast應當易於普通聽眾理解,所以避免使用過多的專業術語或假設聽眾對該話題已有瞭解。如有必要,請思考如何用簡單的術語簡要解釋任何複雜的概念。
利用你的想像力填補輸入文字中的任何空白,或者想出一些值得探討與發人深省的問題,以供Podcast討論。目標是創造一個訊息豐富且娛樂性強的對話,因此可以在你的方法上大膽自由發揮創意。
將你的頭腦風暴想法和Podcast對話的粗略大綱寫在這裡。確保記錄下你希望在結尾重申的主要見解和要點。
</scratchpad>
現在你已經進行了頭腦風暴並建立了一個粗略的大綱,是時候撰寫實際的Podcast對話了。目標是主持人({speaker1_name})與嘉賓({speaker2_name})之間自然、對話式的交流。融入你在頭腦風暴中得出的最佳想法,並確保將任何複雜話題以易於理解的方式解釋清楚。
{language_instruction}
- The podcast should have 2 speakers: {speaker1_name} and {speaker2_name}.
- The podcast should be long.
- The podcast should be interesting, lively, and engaging, and hook the listener from the start.
- The script must be in JSON format.
Follow this JSON example structure, MUST be in {language} language:
{example}
<podcast_dialogue>
根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫你的引人入勝、訊息豐富的Podcast對話。採用對話式的語氣,並包括任何必要的上下文或解釋,使內容對一般聽眾而言容易理解。使用主持人名字 {speaker1_name} 和嘉賓名字 {speaker2_name},以營造更吸引人和身臨其境的聆聽體驗。不要包括像[主持人]或[嘉賓]這樣的括號預留位置。設計你的輸出內容以供直接朗讀——它將直接轉換為音訊。
確保對話儘可能詳細、完整,同時保持在主題之內並維持吸引人的流暢性。目標是使用你的全部輸出容量,建立儘可能長的Podcast節目,同時以有趣的方式傳遞輸入文字中的關鍵訊息。
在對話結束時,讓主持人和嘉賓自然總結他們討論中的主要見解和要點。這應當是對話的隨機部分,以自然隨意而非明顯的總結——目的是在結束前最後一次以自然流暢的方式強化核心思想。最終以感謝詞結束。
</podcast_dialogue>
"""
client = create_client(api_key)
response = client.chat.completions.create(
model="Meta-Llama-3.1-405B-Instruct",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": input_text}
],
temperature=1
)
try:
podcast_match = re.search(r'{.*}', response.choices[0].message.content, re.DOTALL)
if podcast_match:
podcast_json = podcast_match.group(0)
try:
json.loads(podcast_json)
except json.JSONDecodeError:
podcast_json = re.sub(r',\s*}', '}', podcast_json)
podcast_json = re.sub(r',\s*]', ']', podcast_json)
return podcast_json
else:
raise gr.Error("生成 Podcast 劇本失敗!!請稍後再試。")
except Exception as e:
if "API key not valid" in str(e):
raise gr.Error("無效的 API 金鑰!!請提供有效的 API 金鑰。")
elif "rate limit" in str(e).lower():
raise gr.Error("API 金鑰使用額度已超過限制!!請稍後再試或使用其他 API 金鑰。")
else:
raise gr.Error(f"生成 Podcast 劇本失敗!!請稍後再試。")
async def tts_generate(input_text, speaker1, speaker2):
voice_names = {
"家豪 - 臺灣國語 (Male)": "zh-TW-YunJheNeural",
"淑芬 - 臺灣國語 (Female)": "zh-TW-HsiaoChenNeural",
"子晴 - 臺灣國語 (Female)": "zh-TW-HsiaoYuNeural",
"品妍 - 中文 (Female)": "zh-CN-XiaoxiaoNeural",
"美玲 - 中文 (Female)": "zh-CN-XiaoyiNeural",
"建宏 - 中文 (Male)": "zh-CN-YunjianNeural",
"品睿 - 中文 (Male)": "zh-CN-YunxiNeural",
"宥廷 - 中文 (Male)": "zh-CN-YunxiaNeural",
"志明 - 中文 (Male)": "zh-CN-YunyangNeural",
"雨霏 - 中文 (Female)": "zh-CN-liaoning-XiaobeiNeural",
"Andrew - English (Male)": "en-US-AndrewMultilingualNeural",
"Ava - English (Female)": "en-US-AvaMultilingualNeural",
"Brian - English (Male)": "en-US-BrianMultilingualNeural",
"Emma - English (Female)": "en-US-EmmaMultilingualNeural",
"Florian - German (Male)": "de-DE-FlorianMultilingualNeural",
"Seraphina - German (Female)": "de-DE-SeraphinaMultilingualNeural",
"Remy - French (Male)": "fr-FR-RemyMultilingualNeural",
"Vivienne - French (Female)": "fr-FR-VivienneMultilingualNeural"
}
speaker1_voice = voice_names[speaker1]
speaker2_voice = voice_names[speaker2]
try:
podcast_dict = json.loads(input_text)
except json.JSONDecodeError:
cleaned_input = re.sub(r',\s*}', '}', input_text)
cleaned_input = re.sub(r',\s*]', ']', cleaned_input)
podcast_dict = json.loads(cleaned_input)
podcast_json = {
"topic": podcast_dict.get("topic", "Unknown Topic"),
"podcast": []
}
speaker_map = {
1: "speaker1",
2: "speaker2"
}
combined = AudioSegment.empty()
for line in podcast_dict.get("podcast", []):
speaker = line.get("speaker")
text = line.get("line", "")
voice = speaker1_voice if speaker == 1 else speaker2_voice
voice_name = speaker1.split(' - ')[0] if speaker == 1 else speaker2.split(' - ')[0]
communicate = edge_tts.Communicate(text, voice)
audio_file = f"{voice_name}_{uuid.uuid4()}.mp3"
await communicate.save(audio_file)
audio = AudioSegment.from_mp3(audio_file)
combined += audio
os.remove(audio_file)
podcast_json["podcast"].append({
"speaker": speaker_map.get(speaker, speaker),
"line": text
})
output_file = f"Jiangxz_{uuid.uuid4()}.mp3"
combined.export(output_file, format="mp3")
return output_file
async def process_podcast(input_text, language, speaker1, speaker2, api_key):
podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
speaker1_name = speaker1.split(' - ')[0]
speaker2_name = speaker2.split(' - ')[0]
try:
podcast_data = json.loads(podcast_script)
podcast_text = ""
for line in podcast_data.get("podcast", []):
if isinstance(line['speaker'], int):
speaker = speaker1_name if line['speaker'] == 1 else speaker2_name
else:
speaker = line['speaker']
podcast_text += f"{speaker}:{line['line']}\n"
except json.JSONDecodeError:
podcast_text = "Error: Unable to parse the podcast script."
audio_file = await tts_generate(podcast_script, speaker1, speaker2)
return podcast_text, audio_file
custom_css = """
body {
background-color: #f0f8ff;
}
.gradio-container {
background-color: #f0f8ff;
border-radius: 20px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.center-aligned {
text-align: center !important;
color: #ff4081;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.gen-button {
border-radius: 10px !important;
background-color: #ff4081 !important;
color: white !important;
font-weight: bold !important;
transition: all 0.3s ease !important;
margin: 0 !important;
}
.gen-button:hover {
background-color: #f50057 !important;
transform: scale(1.05);
}
.gr-input, .gr-box, .gr-dropdown {
border-radius: 10px !important;
border: 2px solid #ff4081 !important;
margin: 0 !important;
}
.gr-input:focus, .gr-box:focus, .gr-dropdown:focus {
border-color: #f50057 !important;
box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
}
.input-background {
background-color: #B7E0FF !important;
padding: 15px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.lng-background {
background-color: #FFF5CD !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.sk1-background {
background-color: #FFF5CD !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.sk2-background {
background-color: #FFF5CD !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.clear-button {
color: black !important;
background-color: #FFCFB3 !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.api-background {
background-color: #FFCFB3 !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.audio-background {
background-color: #FFF4B5 !important;
padding: 5px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.script-background {
background-color: #FEF9D9 !important;
padding: 15px !important;
border-radius: 10px !important;
margin: 0 !important;
}
"""
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
gr.Markdown("# 🎙️ 自動生成 Podcast 節目及音檔。系統布署:江信宗。 🎙️", elem_classes="center-aligned")
input_text = gr.Textbox(label="請輸入 Podcast 話題(建議50~500字之間)", placeholder="輸入 Podcast 話題內容越詳細劇本越佳 ......", elem_classes="input-background")
with gr.Row():
Language = gr.Dropdown(
choices=["繁體中文", "Auto Detect", "English", "日本語", "한국어"],
value="繁體中文",
label="語言",
interactive=True,
scale=1,
elem_classes="lng-background"
)
speaker_choices = [
"家豪 - 臺灣國語 (Male)",
"淑芬 - 臺灣國語 (Female)",
"子晴 - 臺灣國語 (Female)",
"品睿 - 中文 (Male)",
"品妍 - 中文 (Female)",
"志明 - 中文 (Male)",
"美玲 - 中文 (Female)",
"建宏 - 中文 (Male)",
"宥廷 - 中文 (Male)",
"雨霏 - 中文 (Female)",
"Andrew - English (Male)",
"Ava - English (Female)",
"Brian - English (Male)",
"Emma - English (Female)",
"Florian - German (Male)",
"Seraphina - German (Female)",
"Remy - French (Male)",
"Vivienne - French (Female)"
]
Speaker_1 = gr.Dropdown(
choices=speaker_choices,
value="家豪 - 臺灣國語 (Male)",
label="主持人的語音",
interactive=True,
scale=2,
elem_classes="sk1-background"
)
Speaker_2 = gr.Dropdown(
choices=speaker_choices,
value="品妍 - 中文 (Female)",
label="來賓的語音",
interactive=True,
scale=2,
elem_classes="sk2-background"
)
clear_input_text_button = gr.Button("清除Podcast話題", scale=1, elem_classes="clear-button")
clear_input_text_button.click(fn=lambda: "", inputs=None, outputs=input_text)
with gr.Row():
generate_button = gr.Button("生成 Podcast 節目及音檔", scale=2, elem_classes="gen-button")
api_key = gr.Textbox(label="請輸入您的 API Key", type="password", placeholder="API authentication key for large language models", scale=1, elem_classes="api-background")
audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
podcast_script = gr.Textbox(label="Generated Podcast 劇本", elem_classes="script-background")
generate_button.click(fn=process_podcast, inputs=[input_text, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
if __name__ == "__main__":
if "SPACE_ID" in os.environ:
iface.launch()
else:
iface.launch(share=True, show_api=False)
|