Ryanus commited on
Commit
dcedafb
·
verified ·
1 Parent(s): ebd5e0a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -70
app.py CHANGED
@@ -41,11 +41,11 @@ async def tts_interface(text, voice, rate_percentage, pitch_hz):
41
  def play_saved_audio(audio_file):
42
  return audio_file
43
 
44
- async def podcast_produce(script_list, voice, rate_percentage, pitch_hz, bgm_file, podcast_title, podcast_desc):
45
  rate = f"{'+' if rate_percentage >= 0 else ''}{rate_percentage}%"
46
  pitch = f"{'+' if pitch_hz >= 0 else ''}{pitch_hz}Hz"
47
  audio_segments = []
48
- for idx, text in enumerate(script_list):
49
  if text.strip():
50
  temp_audio = generate_unique_filename(PODCAST_DIR, prefix=f"segment{idx}")
51
  communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
@@ -55,7 +55,7 @@ async def podcast_produce(script_list, voice, rate_percentage, pitch_hz, bgm_fil
55
  if not audio_segments:
56
  return None
57
  podcast_audio = sum(audio_segments)
58
- if bgm_file is not None and os.path.isfile(bgm_file.name):
59
  bgm = AudioSegment.from_file(bgm_file.name).apply_gain(-10)
60
  bgm = bgm[:len(podcast_audio)]
61
  podcast_audio = podcast_audio.overlay(bgm)
@@ -66,24 +66,30 @@ async def podcast_produce(script_list, voice, rate_percentage, pitch_hz, bgm_fil
66
  f.write(f"Title: {podcast_title}\nDescription: {podcast_desc}\n")
67
  return podcast_file
68
 
 
 
 
 
 
 
69
  def add_paragraph(paragraphs):
 
70
  paragraphs.append("")
71
  return paragraphs
72
 
73
  def remove_paragraph(paragraphs):
 
74
  if len(paragraphs) > 1:
75
  paragraphs.pop()
76
  return paragraphs
77
 
78
- def clear_paragraphs():
79
- return [""]
80
-
81
- def clear_textbox():
82
- return ""
83
 
84
- def render_paragraphs(paragraphs):
85
- # 回傳一組 Textbox 的 dict,方便動態更新
86
- return {f"para_{i}": p for i, p in enumerate(paragraphs)}
87
 
88
  async def main():
89
  voices = await get_voices()
@@ -113,57 +119,35 @@ async def main():
113
 
114
  with gr.Tab("播客製作"):
115
  gr.Markdown("### 📝 多段腳本輸入(可自由增減段落)")
116
-
117
  paragraphs_state = gr.State([""])
 
118
 
119
- # 動態產生多個 Textbox,並用 dictionary 存放元件
120
- paragraph_textboxes = {}
 
 
121
 
122
- def render_paragraph_boxes(paragraphs):
123
- boxes = {}
124
- for i, p in enumerate(paragraphs):
125
- boxes[f"para_{i}"] = gr.Textbox(value=p, label=f"段落{i+1}內容", lines=3)
126
- return boxes
127
-
128
- paragraph_textboxes = render_paragraph_boxes(paragraphs_state.value)
129
-
130
- # 按鈕
131
  add_btn = gr.Button("新增段落")
132
  remove_btn = gr.Button("刪除段落")
133
  clear_all_btn = gr.Button("全部清空")
134
 
135
- # 更新段落文字
136
- def update_paragraphs(*texts):
137
- return list(texts)
138
-
139
- # 新增段落
140
  def on_add(paragraphs):
141
- paragraphs.append("")
142
- return paragraphs
143
 
144
- # 刪除段落
145
  def on_remove(paragraphs):
146
- if len(paragraphs) > 1:
147
- paragraphs.pop()
148
- return paragraphs
149
 
150
- # 全部清空
151
  def on_clear():
152
- return [""]
 
153
 
154
- add_btn.click(on_add, inputs=paragraphs_state, outputs=paragraphs_state)
155
- remove_btn.click(on_remove, inputs=paragraphs_state, outputs=paragraphs_state)
156
- clear_all_btn.click(on_clear, outputs=paragraphs_state)
157
-
158
- # 監聽段落文字改變,更新狀態
159
- def on_text_change(*texts):
160
- return list(texts)
161
-
162
- # 將段落文字元件放入列表,方便傳入合成函數
163
- def get_paragraph_values(*args):
164
- return list(args)
165
 
166
- # 播客參數輸入
167
  voice_input2 = gr.Dropdown(voices, label="選擇語音", value="zh-CN-XiaoxiaoNeural")
168
  rate_input2 = gr.Slider(-50, 50, value=0, step=1, label="語速調整 (%)")
169
  pitch_input2 = gr.Slider(-50, 50, value=0, step=1, label="音高調整 (Hz)")
@@ -173,28 +157,12 @@ async def main():
173
  podcast_btn = gr.Button("生成播客")
174
  podcast_output = gr.Audio(type="filepath", label="生成的播客音檔")
175
 
176
- # 動態建立段落輸入元件區塊
177
- with gr.Column() as paragraphs_container:
178
- def build_paragraph_boxes(paragraphs):
179
- boxes = []
180
- for i, p in enumerate(paragraphs):
181
- tb = gr.Textbox(value=p, label=f"段落{i+1}內容", lines=3)
182
- boxes.append(tb)
183
- return boxes
184
-
185
- paragraph_boxes = build_paragraph_boxes(paragraphs_state.value)
186
-
187
- # 重新渲染段落輸入框
188
- def rerender_paragraph_boxes(paragraphs):
189
- nonlocal paragraph_boxes
190
- paragraph_boxes = build_paragraph_boxes(paragraphs)
191
- return paragraph_boxes
192
-
193
- paragraphs_state.change(rerender_paragraph_boxes, inputs=paragraphs_state, outputs=paragraph_boxes)
194
 
195
- # 播客合成按鈕事件
196
  def on_podcast_btn_click(*args):
197
- n = len(paragraph_boxes)
 
198
  scripts = list(args[:n])
199
  voice = args[n]
200
  rate = args[n+1]
@@ -204,8 +172,15 @@ async def main():
204
  desc = args[n+5]
205
  return asyncio.run(podcast_produce(scripts, voice, rate, pitch, bgm, title, desc))
206
 
207
- inputs = paragraph_boxes + [voice_input2, rate_input2, pitch_input2, bgm_input, podcast_title, podcast_desc]
208
- podcast_btn.click(on_podcast_btn_click, inputs=inputs, outputs=podcast_output)
 
 
 
 
 
 
 
209
 
210
  with gr.Tab("檢視已儲存播客"):
211
  podcast_files = gr.Dropdown(list_saved_podcasts(), label="選擇已儲存播客檔案", interactive=True)
 
41
  def play_saved_audio(audio_file):
42
  return audio_file
43
 
44
+ async def podcast_produce(scripts, voice, rate_percentage, pitch_hz, bgm_file, podcast_title, podcast_desc):
45
  rate = f"{'+' if rate_percentage >= 0 else ''}{rate_percentage}%"
46
  pitch = f"{'+' if pitch_hz >= 0 else ''}{pitch_hz}Hz"
47
  audio_segments = []
48
+ for idx, text in enumerate(scripts):
49
  if text.strip():
50
  temp_audio = generate_unique_filename(PODCAST_DIR, prefix=f"segment{idx}")
51
  communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
 
55
  if not audio_segments:
56
  return None
57
  podcast_audio = sum(audio_segments)
58
+ if bgm_file is not None and hasattr(bgm_file, "name") and os.path.isfile(bgm_file.name):
59
  bgm = AudioSegment.from_file(bgm_file.name).apply_gain(-10)
60
  bgm = bgm[:len(podcast_audio)]
61
  podcast_audio = podcast_audio.overlay(bgm)
 
66
  f.write(f"Title: {podcast_title}\nDescription: {podcast_desc}\n")
67
  return podcast_file
68
 
69
+ def clear_textbox():
70
+ return ""
71
+
72
+ def clear_paragraphs():
73
+ return [""]
74
+
75
  def add_paragraph(paragraphs):
76
+ paragraphs = paragraphs.copy()
77
  paragraphs.append("")
78
  return paragraphs
79
 
80
  def remove_paragraph(paragraphs):
81
+ paragraphs = paragraphs.copy()
82
  if len(paragraphs) > 1:
83
  paragraphs.pop()
84
  return paragraphs
85
 
86
+ def update_paragraphs_ui(paragraphs):
87
+ # 回傳一組 Textbox 元件
88
+ return [gr.Textbox(value=p, label=f"段落{i+1}內容", lines=3, interactive=True) for i, p in enumerate(paragraphs)]
 
 
89
 
90
+ def collect_paragraphs(*args):
91
+ # 收集所有段落內容
92
+ return list(args)
93
 
94
  async def main():
95
  voices = await get_voices()
 
119
 
120
  with gr.Tab("播客製作"):
121
  gr.Markdown("### 📝 多段腳本輸入(可自由增減段落)")
 
122
  paragraphs_state = gr.State([""])
123
+ paragraphs_container = gr.Column()
124
 
125
+ # 初始渲染
126
+ paragraph_boxes = update_paragraphs_ui([""])
127
+ for tb in paragraph_boxes:
128
+ paragraphs_container.append(tb)
129
 
 
 
 
 
 
 
 
 
 
130
  add_btn = gr.Button("新增段落")
131
  remove_btn = gr.Button("刪除段落")
132
  clear_all_btn = gr.Button("全部清空")
133
 
 
 
 
 
 
134
  def on_add(paragraphs):
135
+ new_paragraphs = add_paragraph(paragraphs)
136
+ return new_paragraphs, gr.update(components=update_paragraphs_ui(new_paragraphs))
137
 
 
138
  def on_remove(paragraphs):
139
+ new_paragraphs = remove_paragraph(paragraphs)
140
+ return new_paragraphs, gr.update(components=update_paragraphs_ui(new_paragraphs))
 
141
 
 
142
  def on_clear():
143
+ new_paragraphs = clear_paragraphs()
144
+ return new_paragraphs, gr.update(components=update_paragraphs_ui(new_paragraphs))
145
 
146
+ add_btn.click(on_add, inputs=paragraphs_state, outputs=[paragraphs_state, paragraphs_container])
147
+ remove_btn.click(on_remove, inputs=paragraphs_state, outputs=[paragraphs_state, paragraphs_container])
148
+ clear_all_btn.click(on_clear, outputs=[paragraphs_state, paragraphs_container])
 
 
 
 
 
 
 
 
149
 
150
+ # 參數設定
151
  voice_input2 = gr.Dropdown(voices, label="選擇語音", value="zh-CN-XiaoxiaoNeural")
152
  rate_input2 = gr.Slider(-50, 50, value=0, step=1, label="語速調整 (%)")
153
  pitch_input2 = gr.Slider(-50, 50, value=0, step=1, label="音高調整 (Hz)")
 
157
  podcast_btn = gr.Button("生成播客")
158
  podcast_output = gr.Audio(type="filepath", label="生成的播客音檔")
159
 
160
+ def gather_scripts(*args):
161
+ return list(args)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
 
163
  def on_podcast_btn_click(*args):
164
+ # args: 段落內容 + 參數
165
+ n = len(paragraphs_state.value)
166
  scripts = list(args[:n])
167
  voice = args[n]
168
  rate = args[n+1]
 
172
  desc = args[n+5]
173
  return asyncio.run(podcast_produce(scripts, voice, rate, pitch, bgm, title, desc))
174
 
175
+ # 這裡需要用最新的段落數來組合 inputs
176
+ def get_inputs():
177
+ return [tb for tb in paragraphs_container.children] + [voice_input2, rate_input2, pitch_input2, bgm_input, podcast_title, podcast_desc]
178
+
179
+ podcast_btn.click(
180
+ fn=on_podcast_btn_click,
181
+ inputs=lambda: get_inputs(),
182
+ outputs=podcast_output
183
+ )
184
 
185
  with gr.Tab("檢視已儲存播客"):
186
  podcast_files = gr.Dropdown(list_saved_podcasts(), label="選擇已儲存播客檔案", interactive=True)