| import streamlit as st |
| import asyncio |
| from dotenv import load_dotenv |
| load_dotenv() |
| from typing import Union |
| from config import languages, voice_names, default_speaker_1, default_speaker_2, articles |
| from script_generation import MarkdownToScrip, ScriptConfig, PodScript |
| from audio_generation import ScriptToAudio, AudioConfig |
|
|
| |
| script_config = ScriptConfig() |
| audio_config = AudioConfig() |
| |
| for key in ["generation_started", "script_ready", "audio_ready", "pending_generation", "audio_generation_started"]: |
| st.session_state.setdefault(key, False) |
|
|
| |
| async def generate_script(article: str, language: str, voice1: str, voice2: str): |
| generator = MarkdownToScrip(script_config) |
| return await generator.run(article, language, voice1, voice2) |
|
|
| async def generate_audio(script: PodScript, voice1: str, voice2: str): |
| generator = ScriptToAudio(audio_config) |
| return await generator.run(script, voice1, voice2) |
|
|
| |
| def generate_script_sync(article: str, language: str, voice1: str, voice2: str) -> Union[PodScript, None]: |
| return asyncio.run(generate_script(article, language, voice1, voice2)) |
|
|
| def generate_audio_sync(script: PodScript, voice1: str, voice2: str): |
| return asyncio.run(generate_audio(script,voice1, voice2)) |
|
|
|
|
| def render_form(): |
| """Render the form for article selection and voice configuration.""" |
| with st.form("generation_form"): |
| article = st.selectbox("Article", options=articles, format_func=lambda x: x[0]) |
| language = st.selectbox("Langue de sortie", languages, index=0) |
| voice1 = st.selectbox("Voix Speaker 1", voice_names, index=default_speaker_1) |
| voice2 = st.selectbox("Voix Speaker 2", voice_names, index=default_speaker_2) |
|
|
| submitted = st.form_submit_button("📜 Générer le script") |
|
|
| if submitted: |
| st.session_state["generation_started"] = True |
| st.session_state["pending_generation"] = True |
| st.session_state["article"] = article[1] |
| st.session_state["language"] = language |
| st.session_state["voice1"] = voice1 |
| st.session_state["voice2"] = voice2 |
| st.rerun() |
|
|
|
|
| def render_script_panel(): |
| """Render the panel displaying the generated podcast script.""" |
| st.markdown("### 🎧 Script du podcast") |
|
|
| podscript: PodScript = st.session_state["script"] |
| html = """ |
| <div style=' |
| height: 400px; |
| overflow-y: auto; |
| padding: 1em; |
| border: 1px solid #ccc; |
| border-radius: 8px; |
| margin-bottom: 1em; |
| '> |
| """ |
| for line in podscript.conversation: |
| html += f"<p><strong>{line.speaker}</strong> : {line.text}</p>" |
| html += "</div>" |
|
|
| st.markdown(html, unsafe_allow_html=True) |
|
|
|
|
| def render_audio_panel(): |
| """Render the panel for audio generation and playback.""" |
| is_generating = st.session_state.get("audio_generation_started", False) |
| is_done = st.session_state.get("audio_ready", False) |
|
|
| |
| generate_audio_disabled = is_generating or is_done |
|
|
| |
| col1, col2, col3 = st.columns([1, 2, 1]) |
| with col2: |
| clicked = st.button("🎙️ Générer l'audio", key="generate_audio_btn", disabled=generate_audio_disabled) |
|
|
| if clicked: |
| st.session_state["audio_generation_started"] = True |
| st.rerun() |
|
|
| |
| if is_generating and not is_done: |
| with st.spinner("Génération de l’audio en cours...", show_time=True): |
| st.markdown(""" |
| > Cela peut prendre un minute, merci de patienter 🙂. |
| """) |
| audio_path = generate_audio_sync( |
| st.session_state["script"], |
| st.session_state["voice1"], |
| st.session_state["voice2"], |
| ) |
| st.session_state["audio_path"] = audio_path |
| st.session_state["audio_ready"] = True |
| st.session_state["audio_generation_started"] = False |
| st.rerun() |
|
|
| if is_done: |
| st.markdown("### 🔊 Podcast généré") |
| st.audio(st.session_state["audio_path"]) |
|
|
| def render_sidebar(): |
| with st.sidebar: |
| st.title("🎙️ UpVoice") |
| st.markdown("Génèrez un podcast à partir d’un article tech") |
| content = """ |
| |
| * Pour le moment la génération est limitée à un podscast de ~3 minutes. |
| |
| * L'audio est générée via [ gpt4o-mini-tts](https://platform.openai.com/docs/models/gpt-4o-mini-tts), d'autres modèles de génération seront bientôt proposés. |
| |
| * N'hésitez pas à visiter 👉 [Le Blog by Younup](https://www.younup.fr/blog) pour plus de contenu tech !. |
| """ |
| st.markdown(f""" |
| <div style=" |
| border: 1px solid rgba(250, 250, 250, 0.2); |
| border-radius: 10px; |
| padding: 1em; |
| box-shadow: 1px 1px 5px rgba(0,0,0,0.05); |
| margin-bottom: 1em; |
| "> |
| <p style="margin: 0;">{content}</p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| if st.session_state["generation_started"]: |
| if st.button("🔄 Nouvelle génération"): |
| for key in ["generation_started", "script_ready", "audio_ready", "pending_generation", "script", "audio_path", "audio_generation_started"]: |
| st.session_state.pop(key, None) |
| st.rerun() |
|
|
| def main(): |
| st.set_page_config(page_title="UpVoice 🎙️", layout="wide") |
| render_sidebar() |
|
|
| |
| if not st.session_state["generation_started"]: |
| render_form() |
|
|
| |
| if st.session_state.get("pending_generation"): |
| with st.spinner("Génération du script en cours...", show_time=True): |
| st.markdown(""" |
| > Cela peut prendre une trentaine de seconde, merci de patienter 🙂. |
| """) |
| st.session_state["script"] = generate_script_sync( |
| st.session_state["article"], |
| st.session_state["language"], |
| st.session_state["voice1"], |
| st.session_state["voice2"] |
| ) |
| st.session_state["script_ready"] = True |
| st.session_state["pending_generation"] = False |
| st.rerun() |
|
|
| |
| if st.session_state.get("script_ready"): |
| col1, col2 = st.columns([1, 1]) |
| with col1: |
| render_script_panel() |
| with col2: |
| render_audio_panel() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|