Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -20,12 +20,23 @@ from utils import (
|
|
20 |
DialogueItem
|
21 |
)
|
22 |
from prompts import SYSTEM_PROMPT
|
23 |
-
|
|
|
24 |
from qa import transcribe_audio_deepgram, handle_qa_exchange
|
25 |
|
26 |
-
MAX_QA_QUESTIONS = 5
|
27 |
|
28 |
def parse_user_edited_transcript(edited_text: str, host_name: str, guest_name: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
pattern = r"\*\*(.+?)\*\*:\s*(.+)"
|
30 |
matches = re.findall(pattern, edited_text)
|
31 |
|
@@ -60,9 +71,14 @@ def parse_user_edited_transcript(edited_text: str, host_name: str, guest_name: s
|
|
60 |
return items
|
61 |
|
62 |
def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
|
|
|
|
|
|
|
|
|
|
|
63 |
audio_segments = []
|
64 |
transcript = ""
|
65 |
-
crossfade_duration = 50
|
66 |
|
67 |
for item in dialogue_items:
|
68 |
audio_file = generate_audio_mp3(item.text, item.speaker)
|
@@ -106,6 +122,12 @@ def generate_podcast(
|
|
106 |
sponsor_style,
|
107 |
custom_bg_music_path
|
108 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
sources = [bool(file), bool(url), bool(video_url), bool(research_topic_input)]
|
110 |
if sum(sources) > 1:
|
111 |
return None, "Provide only one input (PDF, URL, YouTube, or Topic)."
|
@@ -143,6 +165,7 @@ def generate_podcast(
|
|
143 |
except Exception as e:
|
144 |
return None, f"Error researching topic: {str(e)}"
|
145 |
|
|
|
146 |
text = truncate_text(text)
|
147 |
|
148 |
extra_instructions = []
|
@@ -154,13 +177,10 @@ def generate_podcast(
|
|
154 |
if user_specs.strip():
|
155 |
extra_instructions.append(f"Additional User Instructions: {user_specs}")
|
156 |
|
157 |
-
# If user provided sponsor content, we pass it along; otherwise skip
|
158 |
-
sponsor_instructions_present = False
|
159 |
if sponsor_content.strip():
|
160 |
extra_instructions.append(
|
161 |
f"Sponsor Content Provided (should be under ~30 seconds):\n{sponsor_content}"
|
162 |
)
|
163 |
-
sponsor_instructions_present = True
|
164 |
|
165 |
from prompts import SYSTEM_PROMPT
|
166 |
combined_instructions = "\n\n".join(extra_instructions).strip()
|
@@ -177,7 +197,7 @@ def generate_podcast(
|
|
177 |
f"{length_minutes} Mins",
|
178 |
host_name=host_name or "Jane",
|
179 |
guest_name=guest_name or "John",
|
180 |
-
sponsor_style=sponsor_style
|
181 |
)
|
182 |
except Exception as e:
|
183 |
return None, f"Error generating script: {str(e)}"
|
@@ -230,7 +250,7 @@ def highlight_differences(original: str, edited: str) -> str:
|
|
230 |
|
231 |
def main():
|
232 |
st.set_page_config(
|
233 |
-
page_title="MyPod
|
234 |
layout="centered"
|
235 |
)
|
236 |
|
@@ -238,7 +258,7 @@ def main():
|
|
238 |
with logo_col:
|
239 |
st.image("logomypod.jpg", width=60)
|
240 |
with title_col:
|
241 |
-
st.markdown("## MyPod
|
242 |
|
243 |
st.markdown(
|
244 |
"Welcome to **MyPod**, your go-to AI-powered podcast generator! 🎉\n\n"
|
@@ -246,7 +266,7 @@ def main():
|
|
246 |
"conversational podcast.\n"
|
247 |
"Select a tone and a duration range. The script will be on-topic, concise, and respect your chosen length.\n\n"
|
248 |
"### How to use:\n"
|
249 |
-
"1. **Provide one source:** PDF Files, Website URL, YouTube
|
250 |
"2. **Choose the tone and the target duration.**\n"
|
251 |
"3. **Click 'Generate Podcast'** to produce your podcast. After the audio is generated, "
|
252 |
" you can edit the transcript and re-generate the audio with your edits if needed.\n\n"
|
@@ -271,14 +291,17 @@ def main():
|
|
271 |
st.markdown("### Customize Your Podcast (New Features)")
|
272 |
|
273 |
with st.expander("Set Host & Guest Names/Descriptions (Optional)"):
|
274 |
-
host_name = st.text_input("
|
275 |
-
host_desc = st.text_input("
|
276 |
-
guest_name = st.text_input("
|
277 |
-
guest_desc = st.text_input("
|
278 |
|
279 |
user_specs = st.text_area("Any special instructions or prompts for the script? (Optional)", "")
|
280 |
sponsor_content = st.text_area("Sponsored Content / Ad (Optional)", "")
|
281 |
-
sponsor_style = st.selectbox(
|
|
|
|
|
|
|
282 |
|
283 |
custom_bg_music_file = st.file_uploader("Upload Custom Background Music (Optional)", type=["mp3", "wav"])
|
284 |
custom_bg_music_path = None
|
@@ -293,6 +316,7 @@ def main():
|
|
293 |
st.session_state["transcript"] = None
|
294 |
if "transcript_original" not in st.session_state:
|
295 |
st.session_state["transcript_original"] = None
|
|
|
296 |
if "qa_count" not in st.session_state:
|
297 |
st.session_state["qa_count"] = 0
|
298 |
if "conversation_history" not in st.session_state:
|
@@ -357,6 +381,7 @@ def main():
|
|
357 |
st.session_state["audio_bytes"] = audio_bytes
|
358 |
st.session_state["transcript"] = transcript
|
359 |
st.session_state["transcript_original"] = transcript
|
|
|
360 |
st.session_state["qa_count"] = 0
|
361 |
st.session_state["conversation_history"] = ""
|
362 |
|
@@ -381,6 +406,7 @@ def main():
|
|
381 |
st.session_state["transcript_original"],
|
382 |
edited_text
|
383 |
)
|
|
|
384 |
st.markdown("### **Edited Transcript Highlights**", unsafe_allow_html=True)
|
385 |
st.markdown(highlighted_transcript, unsafe_allow_html=True)
|
386 |
|
@@ -428,9 +454,9 @@ def main():
|
|
428 |
st.markdown("### Updated Transcript")
|
429 |
st.markdown(new_transcript)
|
430 |
|
431 |
-
#
|
432 |
-
#
|
433 |
-
#
|
434 |
st.markdown("## Post-Podcast Q&A")
|
435 |
used_questions = st.session_state["qa_count"]
|
436 |
remaining = MAX_QA_QUESTIONS - used_questions
|
@@ -439,7 +465,6 @@ def main():
|
|
439 |
st.write(f"You can ask up to {remaining} more question(s).")
|
440 |
|
441 |
typed_q = st.text_input("Type your follow-up question:")
|
442 |
-
# Replacing file_uploader with st.audio_input (Streamlit >= 1.41)
|
443 |
audio_q = st.audio_input("Or record an audio question (WAV)")
|
444 |
|
445 |
if st.button("Submit Q&A"):
|
@@ -448,8 +473,9 @@ def main():
|
|
448 |
else:
|
449 |
question_text = typed_q.strip()
|
450 |
if audio_q is not None:
|
451 |
-
|
452 |
-
|
|
|
453 |
local_audio_path = tmp.name
|
454 |
st.write("Transcribing your audio question...")
|
455 |
audio_transcript = transcribe_audio_deepgram(local_audio_path)
|
@@ -472,4 +498,4 @@ def main():
|
|
472 |
|
473 |
|
474 |
if __name__ == "__main__":
|
475 |
-
main()
|
|
|
20 |
DialogueItem
|
21 |
)
|
22 |
from prompts import SYSTEM_PROMPT
|
23 |
+
|
24 |
+
# NEW: For Q&A
|
25 |
from qa import transcribe_audio_deepgram, handle_qa_exchange
|
26 |
|
27 |
+
MAX_QA_QUESTIONS = 5 # up to 5 voice/text questions
|
28 |
|
29 |
def parse_user_edited_transcript(edited_text: str, host_name: str, guest_name: str):
|
30 |
+
"""
|
31 |
+
Looks for lines like:
|
32 |
+
**Angela**: Hello
|
33 |
+
**Dimitris**: Great topic...
|
34 |
+
We treat 'Angela' as the raw display_speaker, 'Hello' as text.
|
35 |
+
Then we map 'Angela' -> speaker='Jane' (if it matches host_name),
|
36 |
+
'Dimitris' -> speaker='John' (if it matches guest_name), etc.
|
37 |
+
|
38 |
+
Returns a list of DialogueItem.
|
39 |
+
"""
|
40 |
pattern = r"\*\*(.+?)\*\*:\s*(.+)"
|
41 |
matches = re.findall(pattern, edited_text)
|
42 |
|
|
|
71 |
return items
|
72 |
|
73 |
def regenerate_audio_from_dialogue(dialogue_items, custom_bg_music_path=None):
|
74 |
+
"""
|
75 |
+
Re-generates multi-speaker audio from user-edited DialogueItems,
|
76 |
+
then mixes with background music or custom music.
|
77 |
+
Returns (audio_bytes, transcript_str).
|
78 |
+
"""
|
79 |
audio_segments = []
|
80 |
transcript = ""
|
81 |
+
crossfade_duration = 50 # ms
|
82 |
|
83 |
for item in dialogue_items:
|
84 |
audio_file = generate_audio_mp3(item.text, item.speaker)
|
|
|
122 |
sponsor_style,
|
123 |
custom_bg_music_path
|
124 |
):
|
125 |
+
"""
|
126 |
+
Creates a multi-speaker podcast from PDF, URL, YouTube, or a research topic.
|
127 |
+
Ensures female voice for host (Jane), male voice for guest (John).
|
128 |
+
Sponsor content is either separate or blended based on sponsor_style.
|
129 |
+
Returns (audio_bytes, transcript_str).
|
130 |
+
"""
|
131 |
sources = [bool(file), bool(url), bool(video_url), bool(research_topic_input)]
|
132 |
if sum(sources) > 1:
|
133 |
return None, "Provide only one input (PDF, URL, YouTube, or Topic)."
|
|
|
165 |
except Exception as e:
|
166 |
return None, f"Error researching topic: {str(e)}"
|
167 |
|
168 |
+
from utils import truncate_text
|
169 |
text = truncate_text(text)
|
170 |
|
171 |
extra_instructions = []
|
|
|
177 |
if user_specs.strip():
|
178 |
extra_instructions.append(f"Additional User Instructions: {user_specs}")
|
179 |
|
|
|
|
|
180 |
if sponsor_content.strip():
|
181 |
extra_instructions.append(
|
182 |
f"Sponsor Content Provided (should be under ~30 seconds):\n{sponsor_content}"
|
183 |
)
|
|
|
184 |
|
185 |
from prompts import SYSTEM_PROMPT
|
186 |
combined_instructions = "\n\n".join(extra_instructions).strip()
|
|
|
197 |
f"{length_minutes} Mins",
|
198 |
host_name=host_name or "Jane",
|
199 |
guest_name=guest_name or "John",
|
200 |
+
sponsor_style=sponsor_style
|
201 |
)
|
202 |
except Exception as e:
|
203 |
return None, f"Error generating script: {str(e)}"
|
|
|
250 |
|
251 |
def main():
|
252 |
st.set_page_config(
|
253 |
+
page_title="MyPod - AI-based Podcast Generator",
|
254 |
layout="centered"
|
255 |
)
|
256 |
|
|
|
258 |
with logo_col:
|
259 |
st.image("logomypod.jpg", width=60)
|
260 |
with title_col:
|
261 |
+
st.markdown("## MyPod - AI powered Podcast Generator")
|
262 |
|
263 |
st.markdown(
|
264 |
"Welcome to **MyPod**, your go-to AI-powered podcast generator! 🎉\n\n"
|
|
|
266 |
"conversational podcast.\n"
|
267 |
"Select a tone and a duration range. The script will be on-topic, concise, and respect your chosen length.\n\n"
|
268 |
"### How to use:\n"
|
269 |
+
"1. **Provide one source:** PDF Files, Website URL, YouTube videos, or a Topic to Research.\n"
|
270 |
"2. **Choose the tone and the target duration.**\n"
|
271 |
"3. **Click 'Generate Podcast'** to produce your podcast. After the audio is generated, "
|
272 |
" you can edit the transcript and re-generate the audio with your edits if needed.\n\n"
|
|
|
291 |
st.markdown("### Customize Your Podcast (New Features)")
|
292 |
|
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 |
+
sponsor_style = st.selectbox(
|
302 |
+
"Sponsor Integration Style",
|
303 |
+
["Separate Break", "Blended"]
|
304 |
+
)
|
305 |
|
306 |
custom_bg_music_file = st.file_uploader("Upload Custom Background Music (Optional)", type=["mp3", "wav"])
|
307 |
custom_bg_music_path = None
|
|
|
316 |
st.session_state["transcript"] = None
|
317 |
if "transcript_original" not in st.session_state:
|
318 |
st.session_state["transcript_original"] = None
|
319 |
+
# For Q&A
|
320 |
if "qa_count" not in st.session_state:
|
321 |
st.session_state["qa_count"] = 0
|
322 |
if "conversation_history" not in st.session_state:
|
|
|
381 |
st.session_state["audio_bytes"] = audio_bytes
|
382 |
st.session_state["transcript"] = transcript
|
383 |
st.session_state["transcript_original"] = transcript
|
384 |
+
# Reset Q&A
|
385 |
st.session_state["qa_count"] = 0
|
386 |
st.session_state["conversation_history"] = ""
|
387 |
|
|
|
406 |
st.session_state["transcript_original"],
|
407 |
edited_text
|
408 |
)
|
409 |
+
|
410 |
st.markdown("### **Edited Transcript Highlights**", unsafe_allow_html=True)
|
411 |
st.markdown(highlighted_transcript, unsafe_allow_html=True)
|
412 |
|
|
|
454 |
st.markdown("### Updated Transcript")
|
455 |
st.markdown(new_transcript)
|
456 |
|
457 |
+
# -----------------------
|
458 |
+
# POST-PODCAST Q&A Logic
|
459 |
+
# -----------------------
|
460 |
st.markdown("## Post-Podcast Q&A")
|
461 |
used_questions = st.session_state["qa_count"]
|
462 |
remaining = MAX_QA_QUESTIONS - used_questions
|
|
|
465 |
st.write(f"You can ask up to {remaining} more question(s).")
|
466 |
|
467 |
typed_q = st.text_input("Type your follow-up question:")
|
|
|
468 |
audio_q = st.audio_input("Or record an audio question (WAV)")
|
469 |
|
470 |
if st.button("Submit Q&A"):
|
|
|
473 |
else:
|
474 |
question_text = typed_q.strip()
|
475 |
if audio_q is not None:
|
476 |
+
suffix = ".wav"
|
477 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
|
478 |
+
tmp.write(audio_q.read())
|
479 |
local_audio_path = tmp.name
|
480 |
st.write("Transcribing your audio question...")
|
481 |
audio_transcript = transcribe_audio_deepgram(local_audio_path)
|
|
|
498 |
|
499 |
|
500 |
if __name__ == "__main__":
|
501 |
+
main()
|