├── LICENSE ├── README.md ├── requirements.txt ├── robark.py ├── robotalk.py └── template.env /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RoboTalk by ZoneSix.io 2 | 3 | The RoboTalk Podcast Creator is an application that helps you conduct research on a given topic, generate podcast scripts based on the researched topic, and create an audio podcast from the generated script. 4 | 5 | ## Video Tutorial 6 | 7 | [![RoboTalk Podcast Creator Demo](https://img.youtube.com/vi/kGj0dKr6H5o/0.jpg)](https://www.youtube.com/watch?v=kGj0dKr6H5o) 8 | 9 | ## Requirements 10 | 11 | This application requires API keys from Google Search Engine (https://console.cloud.google.com/) for current articles to research, https://openai.com for news summarization and script creation, and https://elevenlabs.io for speech generation. 12 | 13 | ## Installation 14 | 15 | To run the application, follow these steps: 16 | 17 | 1. Clone the repository: 18 | 19 | ```shell 20 | git clone https://github.com/ZoneSixGames/RoboTalk.git 21 | ``` 22 | 23 | 2. Navigate to the project directory: 24 | 25 | ```shell 26 | cd RobotTalk 27 | ``` 28 | 29 | 3. Install the required dependencies: 30 | 31 | ```shell 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | 4. Set up environment variables: 36 | 37 | Create a `.env` file in the project directory and add the following environment variables: 38 | 39 | ```shell 40 | OPENAI_API_KEY= 41 | ELEVENLABS_API_KEY= 42 | ELEVENLABS_VOICE_1_ID= 43 | GOOGLE_API_KEY= 44 | CUSTOM_SEARCH_ENGINE_ID= 45 | ``` 46 | 47 | Replace ``, ``, and other placeholders with your actual API keys. 48 | 49 | 5. Run the application: 50 | 51 | ```shell 52 | streamlit run robotalk.py 53 | ``` 54 | 55 | This will start the application, and you can access it in your web browser at the link provided in your console. 56 | 57 | ## Usage 58 | 59 | Once the application is running, you will see a web interface with the following options: 60 | 61 | - **Podcast topic**: Enter the topic for your podcast. 62 | - **Host Name**: Enter the name of the podcast host. 63 | - **Enter the personality for the Host**: Describe the personality of the podcast host. 64 | - **Research**: Click this button to research and summarize top news stories related to the podcast topic. 65 | - **Generate Script**: Click this button to generate a podcast script based on the topic, research, and host's personality. 66 | - **Create Podcast**: Click this button to create an audio podcast from the generated script. 67 | 68 | The application will guide you through each step, and you can view the generated script and research summaries in the corresponding sections. 69 | 70 | 71 | 72 | 73 | 74 | 75 | ## Support 76 | 77 | For any issues or questions, please reach out at contact@zonesix.io 78 | 79 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-decouple 2 | streamlit 3 | python-dotenv 4 | requests 5 | beautifulsoup4 6 | langchain 7 | elevenlabs 8 | urllib3 9 | feedparser 10 | pydub 11 | nltk 12 | bark 13 | openai 14 | google-api-python-client 15 | google-auth-httplib2 16 | google-auth-oauthlib 17 | google-auth 18 | -------------------------------------------------------------------------------- /robark.py: -------------------------------------------------------------------------------- 1 | # Bring in deps 2 | from dotenv import load_dotenv 3 | import os 4 | import streamlit as st 5 | from langchain.llms import OpenAI 6 | from langchain.prompts import PromptTemplate 7 | from langchain.chains import LLMChain, OpenAI 8 | from langchain.utilities import GoogleSearchAPIWrapper 9 | from langchain.memory import ConversationBufferMemory 10 | import urllib.parse 11 | from bark import generate_audio, SAMPLE_RATE, preload_models 12 | import numpy as np 13 | import nltk 14 | from pydub import AudioSegment 15 | import requests 16 | from bs4 import BeautifulSoup 17 | import feedparser 18 | from datetime import datetime 19 | 20 | # Preload Bark models 21 | preload_models() 22 | 23 | # Application Framework 24 | st.title('Zone Six Podcast Creator') 25 | # Collect the inputs 26 | prompt = st.text_input("Enter the podcast topic") 27 | p1_name = st.text_input("Host Name", value='Libby') 28 | p1 = st.text_input("Enter the personality for the Host", value='liberal Democrat woman, background in international affairs and world health, mirroring viewpoints of nicole wallace, Stephen colbert, ali velshi, and christiane amanpour.') 29 | p2_name = st.text_input("Enter second character's name:", value='Be Free') 30 | p2 = st.text_input("Enter the personality for voice 2", value='weed smoking Libertarian bisexual woman from California who loves the environment, technology, and protecting individual freedoms from government overreach, mirroring the views of Spike Cohen, Elon Musk, Carl Sagan, and joe rogan.') 31 | p3_name = st.text_input("Enter third character's name:", value='Q Anon 42') 32 | p3 = st.text_input("Enter the personality for voice 3", value='right wing conservative Republican from Florida, follows government conspiracies on 4chan and reddit, mirroring the views of Milo Yiannopoulos, Ben Shapiro, Steve Bannon, and Tucker Carlson.') 33 | 34 | p1_NAME = p1_name.upper() 35 | p2_NAME = p2_name.upper() 36 | p3_NAME = p3_name.upper() 37 | 38 | # Map character names to voices 39 | VOICE_MAP = { 40 | p1_name: "v2/en_speaker_1", # host voice 41 | p2_name: "v2/en_speaker_2", # guest1 voice 42 | p3_name: "v2/en_speaker_3", # guest2 voice 43 | } 44 | 45 | PODCAST_DIR = None 46 | # Load up the entries as environment variables 47 | load_dotenv() 48 | 49 | # Access the environment variables 50 | API_KEYS = { 51 | 'OPENAI_API_KEY': st.text_input("Enter your OpenAI API key"), 52 | 'GOOGLE_CSE_ID': st.text_input("Enter your Custom Search Engine ID (CSE) key"), 53 | 'GOOGLE_API_KEY': st.text_input("Enter your Google API key"), 54 | } 55 | 56 | # Initialize environment 57 | os.environ.update(API_KEYS) 58 | 59 | # Initialize components 60 | google_search_tool = GoogleSearchAPIWrapper() 61 | 62 | # Initialize OpenAI API 63 | openai_llm = OpenAI(model_name="gpt-3.5-turbo-16k") # Initialize the OpenAI LLM 64 | 65 | # Define templates 66 | title = PromptTemplate.from_template("Write a witty, funny, or ironic podcast title about {topic}.") 67 | script = PromptTemplate.from_template("Write a podcast script based on a given title, research, and unique personalities. Title: {title}, Research: {news_research}, Personalities: Host: {p1_NAME}: {p1}, First Guest: {p2_NAME}: {p2}, Second Guest: {p3_NAME}: {p3}. The podcast should start with the Host giving an introduction and continue with the guest speakers as follows: {p1_NAME}: content n/ {p2_NAME}: Content n/ {p3_NAME}: content n/ and so on, replacing the host and guest names with the input names") 68 | cont_script = PromptTemplate.from_template("Continue writing a podcast script based on a given title, research, recent podcast discussion history. Title: {title}, Research: {research}, Script: {script}") 69 | news = PromptTemplate.from_template("Summarize this news story: {story}") 70 | 71 | # Initialize chains 72 | chains = { 73 | 'title': LLMChain(llm=openai_llm, prompt=title, verbose=True, output_key='title'), 74 | 'script': LLMChain(llm=openai_llm, prompt=script, verbose=True, output_key='script'), 75 | 'cont_script': LLMChain(llm=openai_llm, prompt=cont_script, verbose=True, output_key='cont_script'), 76 | 'news': LLMChain(llm=openai_llm, prompt=news, verbose=True, output_key='summary'), 77 | } 78 | 79 | # Initialize session state for script, research, title if they doesn't exist 80 | if 'script' not in st.session_state: 81 | st.session_state.script = "Script will appear here" 82 | 83 | if 'title' not in st.session_state: 84 | st.session_state.title = "Podcast Title Will Appear Here" 85 | 86 | if 'cont_script' not in st.session_state: 87 | st.session_state.cont_script = "" 88 | 89 | if 'news' not in st.session_state: 90 | st.session_state.news = "" 91 | 92 | if 'research' not in st.session_state: 93 | st.session_state.research = "" 94 | 95 | if 'podcast_dir' not in st.session_state: 96 | st.session_state.podcast_dir = "" 97 | 98 | #Define the functions 99 | 100 | def extract_news_text(url): 101 | nltk.download('punkt') 102 | #"""Extract the text of a news story given its URL.""" 103 | response = requests.get(url) 104 | soup = BeautifulSoup(response.text, 'html.parser') 105 | paragraphs = soup.find_all('p') 106 | 107 | # Concatenate all paragraphs into a single string 108 | story_text = ' '.join([p.get_text() for p in paragraphs]) 109 | 110 | # Tokenize the story_text 111 | tokens = nltk.word_tokenize(story_text) 112 | 113 | # Only keep the first 4000 tokens 114 | tokens = tokens[:4000] 115 | 116 | # Rejoin the tokens into a single string 117 | story_text = ' '.join(tokens) 118 | 119 | return story_text 120 | 121 | def get_top_news_stories(topic, num_stories=5): 122 | """Get the top num_stories news stories on the given topic.""" 123 | # URL encode the topic to ensure it's valid in a URL 124 | topic = urllib.parse.quote_plus(topic) 125 | # Get the feed from the Google News RSS 126 | feed = feedparser.parse(f'https://news.google.com/rss/search?q={topic}') 127 | 128 | # Return the top num_stories stories 129 | return feed.entries[:num_stories] 130 | 131 | def summarize_news_stories(stories): 132 | """Summarize each news story using the OpenAI model.""" 133 | summaries = [] 134 | total_tokens = 0 135 | for story in stories: 136 | # Extract the URL from the story metadata 137 | url = story.get('link', '') 138 | if url: 139 | # Extract the news text 140 | story_text = extract_news_text(url) 141 | 142 | # Generate a summary 143 | summary = chains['news'].run(story_text) 144 | 145 | # Add summary to the list if it doesn't exceed the token limit 146 | summary_tokens = len(summary.split()) # rough token count 147 | if total_tokens + summary_tokens <= 10000: 148 | summaries.append(summary) 149 | total_tokens += summary_tokens 150 | else: 151 | break # stop if we reach the token limit 152 | return summaries 153 | 154 | def create_podcast_directory(): 155 | global PODCAST_DIR 156 | now = datetime.now() # get current date and time 157 | date_time = now.strftime("%Y_%m_%d_%H_%M_%S") # format as a string 158 | podcast_dir = f"Podcast_{date_time}" # prepend "Podcast_" to the string 159 | 160 | if not os.path.exists(podcast_dir): 161 | os.makedirs(podcast_dir) 162 | 163 | PODCAST_DIR = podcast_dir 164 | return PODCAST_DIR # Add this line 165 | 166 | def convert_comments_to_audio(comments): 167 | """Generate audio for each comment in the script.""" 168 | audio_files = [] 169 | silence = np.zeros(int(0.5*SAMPLE_RATE)) 170 | for comment in comments: 171 | voice_id = VOICE_MAP[comment['role']] 172 | audio_array = generate_audio(comment['text'], history_prompt=voice_id) # Use Bark's generate_audio 173 | audio_file = f"{st.session_state.podcast_dir}/{comment['role']}_{comment['order']}.mp3" # Save in podcast directory 174 | audio_array.export(audio_file, format="mp3") # Export as mp3 175 | audio_files.append(audio_file) 176 | return audio_files 177 | 178 | def parse_script(script): 179 | comments = [] 180 | lines = script.split('\n') 181 | for i, line in enumerate(lines): 182 | if ':' in line: 183 | role, content = line.split(':', 1) 184 | if role and content: 185 | role = role.strip().upper() # capitalize role 186 | comments.append({'role': role, 'text': content.strip(), 'order': i}) 187 | return comments 188 | 189 | def validate_inputs(prompt, p1, p2, p3): 190 | return all([prompt, p1, p2, p3]) 191 | 192 | def combine_audio_files(audio_files): 193 | combined = AudioSegment.empty() 194 | for audio_file in sorted(audio_files): 195 | segment = AudioSegment.from_mp3(audio_file) 196 | combined += segment 197 | return combined 198 | 199 | #Operational Structure 200 | if st.button('Generate Script') and validate_inputs(prompt, p1, p2, p3): 201 | # Research and summarize top news stories 202 | stories = get_top_news_stories(prompt) 203 | news_summaries = summarize_news_stories(stories) 204 | st.session_state.research = ' '.join(news_summaries) # Join the list of summaries into a single string 205 | 206 | # Generate title 207 | title_result = chains['title'].run(topic=prompt) 208 | st.session_state.title = title_result # Saving title directly to session state. 209 | 210 | # Generate and display initial script 211 | script_result = chains['script'].run( 212 | title=st.session_state.title, 213 | news_research=st.session_state.research, 214 | p1_NAME=p1_NAME, 215 | p2_NAME=p2_NAME, 216 | p3_NAME=p3_NAME, 217 | p1=p1, 218 | p2=p2, 219 | p3=p3 220 | ) 221 | st.session_state.script = script_result 222 | 223 | # Save the script in the session state and to a text file 224 | st.session_state.podcast_dir = create_podcast_directory() 225 | with open(f"{st.session_state.podcast_dir}/podcast_script.txt", 'w') as f: 226 | f.write(st.session_state.script) 227 | st.success(f"Script saved in {st.session_state.podcast_dir}/podcast_script.txt") 228 | with open(f"{st.session_state.podcast_dir}/podcast_research.txt", 'w') as f: 229 | f.write(st.session_state.research) 230 | st.success(f"Research saved in {st.session_state.podcast_dir}/podcast_research.txt") 231 | 232 | if st.button('Continue Script') and validate_inputs(prompt, p1, p2, p3): 233 | # Generate and display initial script 234 | script_result = chains['cont_script'].run( 235 | title=st.session_state.title, 236 | research=st.session_state.research, 237 | script=st.session_state.script 238 | ) 239 | st.session_state.script += str(script_result) 240 | 241 | # Save the script in the session state and to a text file 242 | with open(f"{st.session_state.podcast_dir}/podcast_script.txt", 'w') as f: 243 | f.write(str(st.session_state.script)) 244 | st.success(f"Script saved in {st.session_state.podcast_dir}/podcast_script.txt") 245 | 246 | 247 | # Download script 248 | st.download_button("Download Script", data='\n'.join(st.session_state.script), file_name='podcast_script.txt', mime='text/plain') 249 | 250 | # Display script from session state 251 | st.write(f'Title: {st.session_state.title}') 252 | st.write(f'Script: \n{st.session_state.script}') 253 | st.write(f'\n{st.session_state.cont_script}') 254 | 255 | print(st.session_state.podcast_dir) 256 | 257 | if st.button('Create Voices') and st.session_state.script: 258 | comments = parse_script('\n'.join(st.session_state.script)) 259 | st.session_state['audio_files'] = convert_comments_to_audio(comments) 260 | for i, audio_file in enumerate(st.session_state['audio_files']): 261 | st.audio(f"{st.session_state.podcast_dir}/podcast.mp3", format='audio/mp3') 262 | 263 | if st.button('Combine Audio') and st.session_state.script: 264 | combined_audio = combine_audio_files(st.session_state['audio_files']) 265 | 266 | combined_audio.export(f"{st.session_state.podcast_dir}/complete_podcast.mp3", format='mp3') 267 | 268 | st.audio(f"{st.session_state.podcast_dir}/complete_podcast.mp3", format='audio/mp3') 269 | 270 | if st.button('Download Podcast') and os.path.exists(f"{st.session_state.podcast_dir}/complete_podcast.mp3"): 271 | with open(f"{st.session_state.podcast_dir}/complete_podcast.mp3", 'rb') as f: 272 | bytes = f.read() 273 | st.download_button("Download Podcast", data=bytes, file_name=f"{st.session_state.podcast_dir}/complete_podcast.mp3", mime='audio/mpeg') 274 | 275 | with st.expander('News Summaries'): 276 | st.write(st.session_state.research) 277 | 278 | with st.expander('Script'): 279 | st.write(st.session_state.title) 280 | st.write(st.session_state.script) 281 | st.write(st.session_state.cont_script) 282 | -------------------------------------------------------------------------------- /robotalk.py: -------------------------------------------------------------------------------- 1 | # Bring in deps 2 | from decouple import config 3 | import os 4 | import streamlit as st 5 | import requests 6 | from bs4 import BeautifulSoup 7 | from langchain import LLMChain, OpenAI # Import the correct class 8 | from langchain.chat_models import ChatOpenAI 9 | from langchain import PromptTemplate 10 | from langchain.utilities import GoogleSearchAPIWrapper 11 | from elevenlabs import generate, save, voices 12 | import urllib.parse 13 | import feedparser 14 | from datetime import datetime 15 | from pydub import AudioSegment 16 | import nltk 17 | 18 | # Access the environment variables 19 | API_KEYS = { 20 | 'OPENAI_API_KEY': config('OPENAI_API_KEY'), 21 | 'ELEVENLABS_API_KEY': config('ELEVENLABS_API_KEY'), 22 | 'ELEVENLABS_VOICE_1_ID': config('ELEVENLABS_VOICE_1_ID'), 23 | 'ELEVENLABS_VOICE_2_ID': config('ELEVENLABS_VOICE_2_ID'), 24 | 'ELEVENLABS_VOICE_3_ID': config('ELEVENLABS_VOICE_3_ID'), 25 | 'ELEVENLABS_VOICE_4_ID': config('ELEVENLABS_VOICE_4_ID'), 26 | 'ELEVENLABS_VOICE_5_ID': config('ELEVENLABS_VOICE_5_ID'), 27 | 'ELEVENLABS_VOICE_6_ID': config('ELEVENLABS_VOICE_6_ID'), 28 | 'ELEVENLABS_VOICE_7_ID': config('ELEVENLABS_VOICE_7_ID'), 29 | 'ELEVENLABS_VOICE_8_ID': config('ELEVENLABS_VOICE_8_ID'), 30 | 'GOOGLE_CSE_ID': config('CUSTOM_SEARCH_ENGINE_ID'), 31 | 'GOOGLE_API_KEY': config('GOOGLE_API_KEY'), 32 | } 33 | 34 | # Application Framework 35 | st.title('RoboTalk Podcast Creator by Zone Six') 36 | 37 | # Collect the inputs 38 | prompt = st.text_input("Enter the podcast topic") 39 | p1_name = st.text_input("Host Name") 40 | p1 = st.text_input("Enter the personality for the Host") 41 | 42 | # Initialize environment 43 | os.environ.update(API_KEYS) 44 | 45 | # Initialize components 46 | google_search_tool = GoogleSearchAPIWrapper() 47 | 48 | # Initialize OpenAI API 49 | openai_llm = ChatOpenAI(model_name="gpt-3.5-turbo-16k") # Initialize the OpenAI LLM 50 | 51 | # Define templates 52 | title = PromptTemplate.from_template("Write a witty, funny, or ironic podcast title about {topic}.") 53 | script = PromptTemplate.from_template("Write a first person editorial podcast based on a given title, research, and unique author personality. Title: {title}, Research: {news_research}, Personality: {p1_name}: {p1}. The article should start by giving an introduction to the topic and then offering an opinion based on the personality of the author. Do not use formal words like 'in conclusion' or 'however' or 'furthermore'.") 54 | # cont_script = PromptTemplate.from_template("Continue writing a podcast script based on a given title, research, recent podcast discussion history. Title: {title}, Research: {research}, Script: {script}") 55 | news = PromptTemplate.from_template("Summarize this news story: {story}") 56 | research = PromptTemplate.from_template("Summarize the research into talking points: {research}") 57 | 58 | # Initialize chains 59 | chains = { 60 | 'title': LLMChain(llm=openai_llm, prompt=title, verbose=True, output_key='title'), 61 | 'script': LLMChain(llm=openai_llm, prompt=script, verbose=True, output_key='script'), 62 | # 'cont_script': LLMChain(llm=openai_llm, prompt=cont_script, verbose=True, output_key='cont_script'), 63 | 'news': LLMChain(llm=openai_llm, prompt=news, verbose=True, output_key='summary'), 64 | 'research': LLMChain(llm=openai_llm, prompt=research, verbose=True, output_key='research'), 65 | } 66 | 67 | # Initialize session state for script, research, title if they don't exist 68 | if 'script' not in st.session_state: 69 | st.session_state.script = "Script will appear here" 70 | 71 | if 'title' not in st.session_state: 72 | st.session_state.title = "Podcast Title Will Appear Here" 73 | 74 | if 'news' not in st.session_state: 75 | st.session_state.news = "" 76 | 77 | if 'research' not in st.session_state: 78 | st.session_state.research = "" 79 | 80 | if 'podcast_dir' not in st.session_state: 81 | st.session_state.podcast_dir = "" 82 | 83 | def extract_news_text(url): 84 | """Extract the text of a news story given its URL.""" 85 | response = requests.get(url) 86 | soup = BeautifulSoup(response.text, 'html.parser') 87 | paragraphs = soup.find_all('p') 88 | 89 | # Concatenate all paragraphs into a single string 90 | story_text = ' '.join([p.get_text() for p in paragraphs]) 91 | 92 | # Tokenize the story_text 93 | tokens = nltk.word_tokenize(story_text) 94 | 95 | # Only keep the first XXXX tokens 96 | tokens = tokens[:2800] 97 | 98 | # Rejoin the tokens into a single string 99 | story_text = ' '.join(tokens) 100 | 101 | return story_text 102 | 103 | def get_top_news_stories(topic, num_stories=5): 104 | """Get the top num_stories news stories on the given topic.""" 105 | # URL encode the topic to ensure it's valid in a URL 106 | topic = urllib.parse.quote_plus(topic) 107 | # Get the feed from the Google News RSS 108 | feed = feedparser.parse(f'https://news.google.com/rss/search?q={topic}') 109 | 110 | # Return the top num_stories stories 111 | return feed.entries[:num_stories] 112 | 113 | def summarize_news_stories(stories): 114 | """Summarize each news story using the OpenAI model.""" 115 | summaries = [] 116 | total_tokens = 0 117 | for story in stories: 118 | # Extract the URL from the story metadata 119 | url = story.get('link', '') 120 | if url: 121 | # Extract the news text 122 | story_text = extract_news_text(url) 123 | 124 | # Generate a summary 125 | summary = chains['news'].run(story_text) 126 | 127 | # Add summary to the list if it doesn't exceed the token limit 128 | summary_tokens = len(summary.split()) # rough token count 129 | if total_tokens + summary_tokens <= 10000: 130 | summaries.append(summary) 131 | total_tokens += summary_tokens 132 | else: 133 | break # stop if we reach the token limit 134 | return summaries 135 | 136 | def validate_inputs(prompt, p1, p1_name): 137 | return all([prompt, p1, p1_name]) 138 | 139 | def create_podcast_directory(): 140 | now = datetime.now() # get current date and time 141 | date_time = now.strftime("%Y_%m_%d_%H_%M_%S") # format as a string 142 | podcast_dir = f"Podcast_{date_time}" # prepend "Podcast_" to the string 143 | 144 | if not os.path.exists(podcast_dir): 145 | os.makedirs(podcast_dir) 146 | 147 | return podcast_dir 148 | 149 | def convert_script_to_audio(script_text, podcast_dir): 150 | selected_voice_id = API_KEYS.get(voice_options[selected_voice]) 151 | print(selected_voice_id) # Add this line to check the selected voice ID 152 | 153 | if selected_voice_id is None: 154 | st.error("Selected voice not found.") 155 | return [] 156 | 157 | audio = generate(text=script_text, api_key=API_KEYS['ELEVENLABS_API_KEY'], voice=selected_voice_id) 158 | audio_file = f"{podcast_dir}/podcast.mp3" # Save in podcast directory 159 | save(audio=audio, filename=audio_file) 160 | print(audio_file) # Add this line to check the audio file path 161 | return [audio_file] # Return a list with one audio file 162 | 163 | # Operational Structure 164 | if st.button('Research') and validate_inputs(prompt, p1, p1_name): 165 | # Research and summarize top news stories 166 | stories = get_top_news_stories(prompt) 167 | news_summaries = summarize_news_stories(stories) 168 | research_summary = chains['research'].run(research=' '.join(news_summaries)) # Use the research chain 169 | st.session_state.research = research_summary # Store the research summary in the session state 170 | st.session_state.podcast_dir = create_podcast_directory() 171 | with open(f"{st.session_state.podcast_dir}/podcast_research.txt", 'w') as f: 172 | f.write(st.session_state.research) 173 | st.success(f"Research saved in {st.session_state.podcast_dir}/podcast_research.txt") 174 | 175 | if st.button('Generate Script') and validate_inputs(prompt, p1, p1_name): 176 | # Generate title 177 | title_result = chains['title'].run(topic=prompt) 178 | st.session_state.title = title_result 179 | 180 | # Generate and display initial script 181 | script_result = chains['script'].run( 182 | title=st.session_state.title, 183 | news_research=st.session_state.research, # Use the research summary 184 | p1_name=p1_name, 185 | p1=p1, 186 | ) 187 | st.session_state.script = script_result 188 | 189 | # Display and edit the script 190 | edited_script = st.text_area('Edit the Script', st.session_state.script, key='edit_script', height=300) 191 | 192 | # Check if the script has been modified 193 | if edited_script != st.session_state.script: 194 | st.session_state.script = edited_script 195 | 196 | # Save the edited script to the session state and to a text file 197 | if st.button('Save Script') and 'edit_script' in st.session_state: 198 | edited_script = st.session_state.edit_script 199 | 200 | # Update the session state with the edited script 201 | st.session_state.script = edited_script 202 | 203 | # Save the edited script to the text file 204 | with open(f"{st.session_state.podcast_dir}/podcast_script.txt", 'w') as f: 205 | f.write(edited_script) 206 | st.success(f"Edited script saved in {st.session_state.podcast_dir}/podcast_script.txt") 207 | # Display the script from the session state 208 | st.write(f'Script: \n{st.session_state.script}') 209 | 210 | # Define the available voice options 211 | voice_options = { 212 | 'Voice 1': 'ELEVENLABS_VOICE_1_ID', 213 | 'Voice 2': 'ELEVENLABS_VOICE_2_ID', 214 | 'Voice 3': 'ELEVENLABS_VOICE_3_ID', 215 | 'Voice 4': 'ELEVENLABS_VOICE_4_ID', 216 | 'Voice 5': 'ELEVENLABS_VOICE_5_ID', 217 | 'Voice 6': 'ELEVENLABS_VOICE_6_ID', 218 | 'Voice 7': 'ELEVENLABS_VOICE_7_ID', 219 | 'Voice 8': 'ELEVENLABS_VOICE_8_ID', 220 | } 221 | 222 | # Allow the user to choose a voice 223 | selected_voice = st.selectbox("Select a voice", list(voice_options.keys())) 224 | 225 | if st.button('Create Podcast') and st.session_state.script: 226 | audio_files = convert_script_to_audio(st.session_state.script, st.session_state.podcast_dir) 227 | if audio_files: 228 | st.audio(audio_files[0], format='audio/mp3') # Use audio_files directly 229 | 230 | with st.expander('News Summaries'): 231 | st.write(st.session_state.research) 232 | 233 | with st.expander('Script'): 234 | st.write(st.session_state.title) 235 | st.write(st.session_state.script) 236 | -------------------------------------------------------------------------------- /template.env: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | ELEVENLABS_API_KEY= 3 | ELEVENLABS_VOICE_1_ID= 4 | ELEVENLABS_VOICE_2_ID= 5 | ELEVENLABS_VOICE_3_ID= 6 | ELEVENLABS_VOICE_4_ID= 7 | ELEVENLABS_VOICE_5_ID= 8 | ELEVENLABS_VOICE_6_ID= 9 | ELEVENLABS_VOICE_7_ID= 10 | ELEVENLABS_VOICE_8_ID= 11 | GOOGLE_API_KEY= 12 | CUSTOM_SEARCH_ENGINE_ID= 13 | --------------------------------------------------------------------------------