siddhartharyaai commited on
Commit
cf2b563
·
verified ·
1 Parent(s): 84a3c5a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -126
app.py CHANGED
@@ -16,41 +16,77 @@ from utils import (
16
  extract_text_from_url,
17
  transcribe_youtube_video,
18
  research_topic,
19
- mix_with_bg_music # We also import the updated function
 
20
  )
21
  from prompts import SYSTEM_PROMPT
22
 
23
-
24
- def parse_user_edited_transcript(edited_text: str):
25
  """
26
  Looks for lines like:
27
- **Jane**: Hello
28
- **John**: Sure, I'd love to talk about that.
29
- Returns a list of (speaker, text).
 
 
 
30
  """
31
- pattern = r"\*\*(Jane|John)\*\*:\s*(.+)"
32
  matches = re.findall(pattern, edited_text)
33
- if not matches:
34
- return [("Jane", edited_text)]
35
- return matches
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
39
  """
40
- Re-generates multi-speaker audio from user-edited text,
41
- then mixes with background music in the root folder (bg_music.mp3)
42
- or a user-provided music file.
43
- Returns final audio bytes and updated transcript.
44
  """
45
  audio_segments = []
46
  transcript = ""
47
  crossfade_duration = 50 # in ms
48
 
49
- for speaker, line_text in dialogue_items:
50
- audio_file = generate_audio_mp3(line_text, speaker)
51
  seg = AudioSegment.from_file(audio_file, format="mp3")
52
  audio_segments.append(seg)
53
- transcript += f"**{speaker}**: {line_text}\n\n"
 
54
  os.remove(audio_file)
55
 
56
  if not audio_segments:
@@ -61,10 +97,8 @@ def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
61
  for seg in audio_segments[1:]:
62
  combined_spoken = combined_spoken.append(seg, crossfade=crossfade_duration)
63
 
64
- # Mix with background music or custom user music
65
  final_mix = mix_with_bg_music(combined_spoken, custom_bg_music_path)
66
 
67
- # Export to bytes
68
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
69
  final_mix.export(temp_audio.name, format="mp3")
70
  final_mp3_path = temp_audio.name
@@ -75,7 +109,6 @@ def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
75
 
76
  return audio_bytes, transcript
77
 
78
-
79
  def generate_podcast(
80
  file,
81
  url,
@@ -93,10 +126,11 @@ def generate_podcast(
93
  ):
94
  """
95
  Creates a multi-speaker podcast from PDF, URL, YouTube, or a research topic.
96
- Returns (audio_bytes, transcript_str), mixing with background music or user-provided music.
97
- """
98
 
99
- # Ensure only one input source
 
100
  sources = [bool(file), bool(url), bool(video_url), bool(research_topic_input)]
101
  if sum(sources) > 1:
102
  return None, "Provide only one input (PDF, URL, YouTube, or Research topic)."
@@ -108,7 +142,6 @@ def generate_podcast(
108
  try:
109
  if not file.name.lower().endswith('.pdf'):
110
  return None, "Please upload a PDF file."
111
- # Use the file-like object directly to read the PDF
112
  reader = pypdf.PdfReader(file)
113
  text = " ".join(page.extract_text() for page in reader.pages if page.extract_text())
114
  except Exception as e:
@@ -138,41 +171,38 @@ def generate_podcast(
138
  # Truncate if needed
139
  text = truncate_text(text)
140
 
141
- # Incorporate user-specified host/guest details, user specs, sponsor content into the system prompt
142
- # We'll compile an "extra_instructions" string that we feed to generate_script
143
  extra_instructions = []
144
 
145
- # (1) Host/Guest details
146
  if host_name or guest_name:
147
- host_str = f"Host: {host_name or 'Jane'} - {host_desc or 'a curious podcast host'}."
148
- guest_str = f"Guest: {guest_name or 'John'} - {guest_desc or 'an expert in the subject matter'}."
149
- extra_instructions.append(f"{host_str}\n{guest_str}")
150
 
151
- # (2) User custom specs
152
  if user_specs.strip():
153
  extra_instructions.append(f"Additional User Instructions: {user_specs}")
154
 
155
- # (3) Sponsor content
156
  if sponsor_content.strip():
157
  extra_instructions.append(
158
  "Please include a short sponsored advertisement. The sponsor text is as follows:\n"
159
  + sponsor_content
160
  )
161
 
162
- # Combine all extra instructions
163
  combined_instructions = "\n\n".join(extra_instructions).strip()
164
-
165
- # Construct a new system prompt:
166
- # We'll keep the existing SYSTEM_PROMPT, but add the combined_instructions at the end.
167
- # The 'generate_script' function will handle injecting tone and length logic.
168
  full_prompt = SYSTEM_PROMPT
169
  if combined_instructions:
170
  full_prompt += f"\n\n# Additional Instructions\n{combined_instructions}\n"
171
 
172
- # For up to 1 hour generation, we map minutes to word ranges in generate_script.
173
- # We'll pass length_minutes to the function directly now.
174
  try:
175
- script = generate_script(full_prompt, text, tone, f"{length_minutes} Mins")
 
 
 
 
 
 
 
176
  except Exception as e:
177
  return None, f"Error generating script: {str(e)}"
178
 
@@ -182,10 +212,12 @@ def generate_podcast(
182
 
183
  try:
184
  for item in script.dialogue:
 
 
185
  audio_file = generate_audio_mp3(item.text, item.speaker)
186
  seg = AudioSegment.from_file(audio_file, format="mp3")
187
  audio_segments.append(seg)
188
- transcript += f"**{item.speaker}**: {item.text}\n\n"
189
  os.remove(audio_file)
190
 
191
  if not audio_segments:
@@ -195,7 +227,6 @@ def generate_podcast(
195
  for seg in audio_segments[1:]:
196
  combined_spoken = combined_spoken.append(seg, crossfade=crossfade_duration)
197
 
198
- # Mix with bg music or user-provided custom music
199
  final_mix = mix_with_bg_music(combined_spoken, custom_bg_music_path)
200
 
201
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
@@ -211,7 +242,6 @@ def generate_podcast(
211
  except Exception as e:
212
  return None, f"Error generating audio: {str(e)}"
213
 
214
-
215
  def highlight_differences(original: str, edited: str) -> str:
216
  """
217
  Highlights the differences between the original and edited transcripts.
@@ -221,22 +251,17 @@ def highlight_differences(original: str, edited: str) -> str:
221
  highlighted = []
222
  for opcode, i1, i2, j1, j2 in matcher.get_opcodes():
223
  if opcode == 'equal':
224
- # Unchanged words
225
  highlighted.extend(original.split()[i1:i2])
226
  elif opcode in ('replace', 'insert'):
227
- # Added or replaced words - highlight in red
228
  added_words = edited.split()[j1:j2]
229
  highlighted.extend([f'<span style="color:red">{word}</span>' for word in added_words])
230
  elif opcode == 'delete':
231
- # Deleted words - optionally, can be shown differently
232
  pass
233
  return ' '.join(highlighted)
234
 
235
-
236
  def main():
237
  st.set_page_config(page_title="MyPod - AI-based Podcast Generator", layout="centered")
238
 
239
- # Use smaller font for the main header
240
  st.markdown("## MyPod - AI powered Podcast Generator")
241
 
242
  st.markdown(
@@ -247,7 +272,6 @@ def main():
247
  "1. **Provide one source:** PDF Files, Website URL, YouTube link or a Topic to Research.\n"
248
  "2. **Choose the tone and the target duration.**\n"
249
  "3. **Click 'Generate Podcast'** to produce your podcast. After the audio is generated, you can edit the transcript and re-generate the audio with your edits if needed.\n\n"
250
- "**Research a Topic:** If it's too niche or specific, you might not get the desired outcome.\n\n"
251
  "**Token Limit:** Up to ~2,048 tokens are supported. Long inputs may be truncated.\n"
252
  "**Note:** YouTube videos will only work if they have captions built in.\n\n"
253
  "⏳**Please be patient while your podcast is being generated.** This process involves content analysis, script creation, "
@@ -263,37 +287,25 @@ def main():
263
  with col2:
264
  research_topic_input = st.text_input("Or Research a Topic")
265
  tone = st.radio("Tone", ["Humorous", "Formal", "Casual", "Youthful"], index=2)
266
-
267
- # (3) A slider for length (1 to 60 mins)
268
  length_minutes = st.slider("Podcast Length (in minutes)", 1, 60, 3)
269
 
270
- # Additional user options for the new features:
271
  st.markdown("### Customize Your Podcast (New Features)")
272
- # (1) Host/Guest customization
273
  with st.expander("Set Host & Guest Names/Descriptions (Optional)"):
274
  host_name = st.text_input("Host Name (leave blank for 'Jane')")
275
  host_desc = st.text_input("Host Description (Optional)")
276
  guest_name = st.text_input("Guest Name (leave blank for 'John')")
277
  guest_desc = st.text_input("Guest Description (Optional)")
278
 
279
- # (2) User custom specs/prompts
280
  user_specs = st.text_area("Any special instructions or prompts for the script? (Optional)", "")
281
-
282
- # (4) Sponsored content
283
  sponsor_content = st.text_area("Sponsored Content / Ad (Optional)", "")
284
 
285
- # (5) Custom music upload
286
  custom_bg_music_file = st.file_uploader("Upload Custom Background Music (Optional)", type=["mp3", "wav"])
287
-
288
- # We'll store the path to a temp file if the user uploads custom music
289
  custom_bg_music_path = None
290
  if custom_bg_music_file:
291
- # Save to a temp file
292
  with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(custom_bg_music_file.name)[1]) as tmp:
293
  tmp.write(custom_bg_music_file.read())
294
  custom_bg_music_path = tmp.name
295
 
296
- # Store results in session_state
297
  if "audio_bytes" not in st.session_state:
298
  st.session_state["audio_bytes"] = None
299
  if "transcript" not in st.session_state:
@@ -307,27 +319,26 @@ def main():
307
  progress_bar = st.progress(0)
308
  progress_text = st.empty()
309
 
310
- progress_messages = [
311
  "🔍 Analyzing your input...",
312
  "📝 Crafting the perfect script...",
313
  "🎙️ Generating high-quality audio...",
314
  "🎶 Adding the finishing touches..."
315
  ]
316
 
317
- # Incremental updates
318
- progress_text.write(progress_messages[0])
319
  progress_bar.progress(0)
320
  time.sleep(1.0)
321
 
322
- progress_text.write(progress_messages[1])
323
  progress_bar.progress(25)
324
  time.sleep(1.0)
325
 
326
- progress_text.write(progress_messages[2])
327
  progress_bar.progress(50)
328
  time.sleep(1.0)
329
 
330
- progress_text.write(progress_messages[3])
331
  progress_bar.progress(75)
332
  time.sleep(1.0)
333
 
@@ -371,22 +382,19 @@ def main():
371
  )
372
 
373
  st.markdown("### Generated Transcript (Editable)")
374
-
375
  edited_text = st.text_area(
376
  "Feel free to tweak lines, fix errors, or reword anything.",
377
  value=st.session_state["transcript"],
378
  height=300
379
  )
380
 
381
- # Compute differences and highlight
382
  if st.session_state["transcript_original"]:
383
- highlighted_transcript = highlight_differences(
384
  st.session_state["transcript_original"],
385
  edited_text
386
  )
387
-
388
  st.markdown("### **Edited Transcript Highlights**", unsafe_allow_html=True)
389
- st.markdown(highlighted_transcript, unsafe_allow_html=True)
390
 
391
  if st.button("Regenerate Audio From Edited Text"):
392
  regen_bar = st.progress(0)
@@ -400,7 +408,9 @@ def main():
400
  regen_bar.progress(50)
401
  time.sleep(1.0)
402
 
403
- dialogue_items = parse_user_edited_transcript(edited_text)
 
 
404
  new_audio_bytes, new_transcript = regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path)
405
 
406
  regen_bar.progress(75)
@@ -428,58 +438,5 @@ def main():
428
  st.markdown("### Updated Transcript")
429
  st.markdown(new_transcript)
430
 
431
-
432
- # ---------------------------------------------------------------------
433
- # Overriding the local function to keep consistent usage:
434
- # ---------------------------------------------------------------------
435
- def mix_with_bg_music(spoken: AudioSegment, custom_bg_music_path=None) -> AudioSegment:
436
- """
437
- Mixes 'spoken' with bg_music.mp3 or a custom music file:
438
- 1) Start with 2 seconds of music alone before speech begins.
439
- 2) Loop the music if it's shorter than the final audio length.
440
- 3) Lower the music volume so the speech is clear.
441
- """
442
- if custom_bg_music_path:
443
- music_path = custom_bg_music_path
444
- else:
445
- music_path = "bg_music.mp3" # default in root folder
446
-
447
- try:
448
- bg_music = AudioSegment.from_file(music_path)
449
- except Exception as e:
450
- print("[ERROR] Failed to load background music:", e)
451
- return spoken
452
-
453
- bg_music = bg_music - 14.0 # Lower volume (e.g. -14 dB)
454
-
455
- total_length_ms = len(spoken) + 2000
456
- looped_music = AudioSegment.empty()
457
- while len(looped_music) < total_length_ms:
458
- looped_music += bg_music
459
-
460
- looped_music = looped_music[:total_length_ms]
461
-
462
- final_mix = looped_music.overlay(spoken, position=2000)
463
- return final_mix
464
-
465
-
466
- def highlight_differences(original: str, edited: str) -> str:
467
- """
468
- Highlights the differences between the original and edited transcripts.
469
- Added or modified words are wrapped in <span> tags with red color.
470
- """
471
- matcher = difflib.SequenceMatcher(None, original.split(), edited.split())
472
- highlighted = []
473
- for opcode, i1, i2, j1, j2 in matcher.get_opcodes():
474
- if opcode == 'equal':
475
- highlighted.extend(original.split()[i1:i2])
476
- elif opcode in ('replace', 'insert'):
477
- added_words = edited.split()[j1:j2]
478
- highlighted.extend([f'<span style="color:red">{word}</span>' for word in added_words])
479
- elif opcode == 'delete':
480
- pass
481
- return ' '.join(highlighted)
482
-
483
-
484
  if __name__ == "__main__":
485
  main()
 
16
  extract_text_from_url,
17
  transcribe_youtube_video,
18
  research_topic,
19
+ mix_with_bg_music,
20
+ DialogueItem # so we can construct items
21
  )
22
  from prompts import SYSTEM_PROMPT
23
 
24
+ def parse_user_edited_transcript(edited_text: str, host_name: str, guest_name: str):
 
25
  """
26
  Looks for lines like:
27
+ **Angela**: Hello
28
+ **Dimitris**: Great topic...
29
+ We treat 'Angela' as the raw display_speaker, 'Hello' as text.
30
+ Then we map 'Angela' -> speaker='Jane' if it matches host_name (case-insensitive),
31
+ 'Dimitris' -> speaker='John' if it matches guest_name, else default to 'Jane'.
32
+ Returns a list of (DialogueItem).
33
  """
34
+ pattern = r"\*\*(.+?)\*\*:\s*(.+)"
35
  matches = re.findall(pattern, edited_text)
 
 
 
36
 
37
+ items = []
38
+ if not matches:
39
+ # No lines found, treat entire text as if it's host
40
+ raw_name = host_name or "Jane"
41
+ text_line = edited_text.strip()
42
+ speaker = "Jane"
43
+ if raw_name.lower() == guest_name.lower():
44
+ speaker = "John"
45
+ # build a single item
46
+ item = DialogueItem(
47
+ speaker=speaker,
48
+ display_speaker=raw_name,
49
+ text=text_line
50
+ )
51
+ items.append(item)
52
+ return items
53
+
54
+ # If we have multiple lines
55
+ for (raw_name, text_line) in matches:
56
+ # Map to TTS speaker
57
+ if raw_name.lower() == host_name.lower():
58
+ # host -> female
59
+ speaker = "Jane"
60
+ elif raw_name.lower() == guest_name.lower():
61
+ # guest -> male
62
+ speaker = "John"
63
+ else:
64
+ # unknown -> default to female host
65
+ speaker = "Jane"
66
+ item = DialogueItem(
67
+ speaker=speaker,
68
+ display_speaker=raw_name,
69
+ text=text_line
70
+ )
71
+ items.append(item)
72
+ return items
73
 
74
  def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
75
  """
76
+ Re-generates multi-speaker audio from user-edited DialogueItems,
77
+ then mixes with background music (bg_music.mp3) or custom music.
78
+ Returns final audio bytes and updated transcript (using display_speaker).
 
79
  """
80
  audio_segments = []
81
  transcript = ""
82
  crossfade_duration = 50 # in ms
83
 
84
+ for item in dialogue_items:
85
+ audio_file = generate_audio_mp3(item.text, item.speaker)
86
  seg = AudioSegment.from_file(audio_file, format="mp3")
87
  audio_segments.append(seg)
88
+ # Use item.display_speaker for the text transcript
89
+ transcript += f"**{item.display_speaker}**: {item.text}\n\n"
90
  os.remove(audio_file)
91
 
92
  if not audio_segments:
 
97
  for seg in audio_segments[1:]:
98
  combined_spoken = combined_spoken.append(seg, crossfade=crossfade_duration)
99
 
 
100
  final_mix = mix_with_bg_music(combined_spoken, custom_bg_music_path)
101
 
 
102
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
103
  final_mix.export(temp_audio.name, format="mp3")
104
  final_mp3_path = temp_audio.name
 
109
 
110
  return audio_bytes, transcript
111
 
 
112
  def generate_podcast(
113
  file,
114
  url,
 
126
  ):
127
  """
128
  Creates a multi-speaker podcast from PDF, URL, YouTube, or a research topic.
129
+ Uses female voice (Jane) for host, male voice (John) for guest.
130
+ Display_speaker is user-chosen name, speaker is "Jane" or "John".
131
 
132
+ Returns (audio_bytes, transcript_str).
133
+ """
134
  sources = [bool(file), bool(url), bool(video_url), bool(research_topic_input)]
135
  if sum(sources) > 1:
136
  return None, "Provide only one input (PDF, URL, YouTube, or Research topic)."
 
142
  try:
143
  if not file.name.lower().endswith('.pdf'):
144
  return None, "Please upload a PDF file."
 
145
  reader = pypdf.PdfReader(file)
146
  text = " ".join(page.extract_text() for page in reader.pages if page.extract_text())
147
  except Exception as e:
 
171
  # Truncate if needed
172
  text = truncate_text(text)
173
 
174
+ # Build extra instructions
 
175
  extra_instructions = []
176
 
 
177
  if host_name or guest_name:
178
+ h = f"Host: {host_name or 'Jane'} - {host_desc or 'a curious host'}."
179
+ g = f"Guest: {guest_name or 'John'} - {guest_desc or 'an expert'}."
180
+ extra_instructions.append(f"{h}\n{g}")
181
 
 
182
  if user_specs.strip():
183
  extra_instructions.append(f"Additional User Instructions: {user_specs}")
184
 
 
185
  if sponsor_content.strip():
186
  extra_instructions.append(
187
  "Please include a short sponsored advertisement. The sponsor text is as follows:\n"
188
  + sponsor_content
189
  )
190
 
 
191
  combined_instructions = "\n\n".join(extra_instructions).strip()
 
 
 
 
192
  full_prompt = SYSTEM_PROMPT
193
  if combined_instructions:
194
  full_prompt += f"\n\n# Additional Instructions\n{combined_instructions}\n"
195
 
196
+ # Use "generate_script" with host/guest name so it can do the mapping
 
197
  try:
198
+ script = generate_script(
199
+ full_prompt,
200
+ text,
201
+ tone,
202
+ f"{length_minutes} Mins",
203
+ host_name=host_name or "Jane",
204
+ guest_name=guest_name or "John"
205
+ )
206
  except Exception as e:
207
  return None, f"Error generating script: {str(e)}"
208
 
 
212
 
213
  try:
214
  for item in script.dialogue:
215
+ # item.speaker is guaranteed "Jane" or "John"
216
+ # item.display_speaker is the user-facing name
217
  audio_file = generate_audio_mp3(item.text, item.speaker)
218
  seg = AudioSegment.from_file(audio_file, format="mp3")
219
  audio_segments.append(seg)
220
+ transcript += f"**{item.display_speaker}**: {item.text}\n\n"
221
  os.remove(audio_file)
222
 
223
  if not audio_segments:
 
227
  for seg in audio_segments[1:]:
228
  combined_spoken = combined_spoken.append(seg, crossfade=crossfade_duration)
229
 
 
230
  final_mix = mix_with_bg_music(combined_spoken, custom_bg_music_path)
231
 
232
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
 
242
  except Exception as e:
243
  return None, f"Error generating audio: {str(e)}"
244
 
 
245
  def highlight_differences(original: str, edited: str) -> str:
246
  """
247
  Highlights the differences between the original and edited transcripts.
 
251
  highlighted = []
252
  for opcode, i1, i2, j1, j2 in matcher.get_opcodes():
253
  if opcode == 'equal':
 
254
  highlighted.extend(original.split()[i1:i2])
255
  elif opcode in ('replace', 'insert'):
 
256
  added_words = edited.split()[j1:j2]
257
  highlighted.extend([f'<span style="color:red">{word}</span>' for word in added_words])
258
  elif opcode == 'delete':
 
259
  pass
260
  return ' '.join(highlighted)
261
 
 
262
  def main():
263
  st.set_page_config(page_title="MyPod - AI-based Podcast Generator", layout="centered")
264
 
 
265
  st.markdown("## MyPod - AI powered Podcast Generator")
266
 
267
  st.markdown(
 
272
  "1. **Provide one source:** PDF Files, Website URL, YouTube link or a Topic to Research.\n"
273
  "2. **Choose the tone and the target duration.**\n"
274
  "3. **Click 'Generate Podcast'** to produce your podcast. After the audio is generated, you can edit the transcript and re-generate the audio with your edits if needed.\n\n"
 
275
  "**Token Limit:** Up to ~2,048 tokens are supported. Long inputs may be truncated.\n"
276
  "**Note:** YouTube videos will only work if they have captions built in.\n\n"
277
  "⏳**Please be patient while your podcast is being generated.** This process involves content analysis, script creation, "
 
287
  with col2:
288
  research_topic_input = st.text_input("Or Research a Topic")
289
  tone = st.radio("Tone", ["Humorous", "Formal", "Casual", "Youthful"], index=2)
 
 
290
  length_minutes = st.slider("Podcast Length (in minutes)", 1, 60, 3)
291
 
 
292
  st.markdown("### Customize Your Podcast (New Features)")
 
293
  with st.expander("Set Host & Guest Names/Descriptions (Optional)"):
294
  host_name = st.text_input("Host Name (leave blank for 'Jane')")
295
  host_desc = st.text_input("Host Description (Optional)")
296
  guest_name = st.text_input("Guest Name (leave blank for 'John')")
297
  guest_desc = st.text_input("Guest Description (Optional)")
298
 
 
299
  user_specs = st.text_area("Any special instructions or prompts for the script? (Optional)", "")
 
 
300
  sponsor_content = st.text_area("Sponsored Content / Ad (Optional)", "")
301
 
 
302
  custom_bg_music_file = st.file_uploader("Upload Custom Background Music (Optional)", type=["mp3", "wav"])
 
 
303
  custom_bg_music_path = None
304
  if custom_bg_music_file:
 
305
  with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(custom_bg_music_file.name)[1]) as tmp:
306
  tmp.write(custom_bg_music_file.read())
307
  custom_bg_music_path = tmp.name
308
 
 
309
  if "audio_bytes" not in st.session_state:
310
  st.session_state["audio_bytes"] = None
311
  if "transcript" not in st.session_state:
 
319
  progress_bar = st.progress(0)
320
  progress_text = st.empty()
321
 
322
+ messages = [
323
  "🔍 Analyzing your input...",
324
  "📝 Crafting the perfect script...",
325
  "🎙️ Generating high-quality audio...",
326
  "🎶 Adding the finishing touches..."
327
  ]
328
 
329
+ progress_text.write(messages[0])
 
330
  progress_bar.progress(0)
331
  time.sleep(1.0)
332
 
333
+ progress_text.write(messages[1])
334
  progress_bar.progress(25)
335
  time.sleep(1.0)
336
 
337
+ progress_text.write(messages[2])
338
  progress_bar.progress(50)
339
  time.sleep(1.0)
340
 
341
+ progress_text.write(messages[3])
342
  progress_bar.progress(75)
343
  time.sleep(1.0)
344
 
 
382
  )
383
 
384
  st.markdown("### Generated Transcript (Editable)")
 
385
  edited_text = st.text_area(
386
  "Feel free to tweak lines, fix errors, or reword anything.",
387
  value=st.session_state["transcript"],
388
  height=300
389
  )
390
 
 
391
  if st.session_state["transcript_original"]:
392
+ highlighted = highlight_differences(
393
  st.session_state["transcript_original"],
394
  edited_text
395
  )
 
396
  st.markdown("### **Edited Transcript Highlights**", unsafe_allow_html=True)
397
+ st.markdown(highlighted, unsafe_allow_html=True)
398
 
399
  if st.button("Regenerate Audio From Edited Text"):
400
  regen_bar = st.progress(0)
 
408
  regen_bar.progress(50)
409
  time.sleep(1.0)
410
 
411
+ # Parse lines, map to DialogueItem with correct TTS speaker
412
+ # host => female (Jane), guest => male (John)
413
+ dialogue_items = parse_user_edited_transcript(edited_text, host_name or "Jane", guest_name or "John")
414
  new_audio_bytes, new_transcript = regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path)
415
 
416
  regen_bar.progress(75)
 
438
  st.markdown("### Updated Transcript")
439
  st.markdown(new_transcript)
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  if __name__ == "__main__":
442
  main()