eswardivi commited on
Commit
1a44f6e
Β·
verified Β·
1 Parent(s): 659ac74

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -0
app.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import os
4
+ import xml.etree.ElementTree as ET
5
+ import openai
6
+ import edge_tts
7
+ from pydub import AudioSegment
8
+ import re
9
+ import time
10
+ import asyncio
11
+ import streamlit as st
12
+ from datetime import datetime
13
+ from dotenv import load_dotenv
14
+
15
+ load_dotenv()
16
+
17
+ PODCAST_FILE = "merged_audio.mp3"
18
+ SHOW_NOTES_FILE = "show_notes.txt"
19
+ CONVERSATION_FILE = "conversation.json"
20
+ LAST_RUN_FILE = "last_run.txt"
21
+
22
+ feed_url = "http://papers.takara.ai/api/feed"
23
+ response = requests.get(feed_url)
24
+ tree = ET.ElementTree(ET.fromstring(response.text))
25
+
26
+ daily_feed = ""
27
+ items = []
28
+ for item in tree.iter("item"):
29
+ title = item.find("title").text
30
+ link = item.find("link").text
31
+ description = item.find("description").text
32
+ items.append({"title": title, "link": link, "description": description})
33
+ daily_feed += f"Title: {title.strip()}\nDescription: {description}\n\n"
34
+
35
+ # OpenAI API client
36
+ client = openai.Client(
37
+ api_key=os.getenv("DEEPINFRA_API"),
38
+ base_url="https://api.deepinfra.com/v1/openai",
39
+ )
40
+
41
+
42
+ def build_prompt(text, items):
43
+ """Generates a concise podcast conversation covering all research papers, ensuring correct JSON format."""
44
+ paper_summaries = "\n".join(
45
+ [f"- {item['title']} ({item['link']}): {item['description']}" for item in items]
46
+ )
47
+
48
+ # JSON Template for correct formatting
49
+ template = """
50
+ {
51
+ "conversation": [
52
+ {"speaker": "Brian", "text": ""},
53
+ {"speaker": "Jenny", "text": ""}
54
+ ]
55
+ }
56
+ """
57
+
58
+ return (
59
+ f"πŸŽ™οΈ Welcome to Daily Papers! Today, we're diving into the latest AI research in a quick, engaging, and "
60
+ f"informative discussion. This podcast should be **under 5 minutes**, so keep it concise while still covering "
61
+ f"the key points of each paper.\n\n"
62
+ f"Here are today's research papers:\n{paper_summaries}\n\n"
63
+ f"Convert this into a **short, engaging** podcast conversation between two experts, Brian and Jenny. "
64
+ f"Use a **natural tone, casual phrasing, and occasional filler words like 'uhm' and 'you know'** to keep it realistic. "
65
+ f"Summarize the key insights **briefly** without excessive details. Prioritize a fast-paced but informative flow. "
66
+ f"Ensure all papers are **mentioned**, but do not spend much time on each.\n\n"
67
+ f"Please follow this exact JSON format:\n{template}"
68
+ )
69
+
70
+
71
+
72
+ def extract_conversation(text, items, max_retries=3):
73
+ """Extracts podcast conversation from OpenAI API with retries."""
74
+ for attempt in range(1, max_retries + 1):
75
+ try:
76
+ print(f"Attempt {attempt} to generate conversation...")
77
+ chat_completion = client.chat.completions.create(
78
+ messages=[{"role": "user", "content": build_prompt(text, items)}],
79
+ model="meta-llama/Llama-3.3-70B-Instruct-Turbo",
80
+ temperature=0.7,
81
+ max_tokens=4096,
82
+ )
83
+ pattern = r"\{(?:[^{}]|(?:\{[^{}]*\}))*\}"
84
+ json_match = re.search(pattern, chat_completion.choices[0].message.content)
85
+ if json_match:
86
+ return json.loads(json_match.group())
87
+ raise ValueError("No valid JSON found in response")
88
+ except Exception as e:
89
+ print(f"Error: {e}")
90
+ if attempt < max_retries:
91
+ time.sleep(2)
92
+ else:
93
+ raise RuntimeError(f"Failed after {max_retries} attempts.")
94
+
95
+ def generate_show_notes(items):
96
+ """Creates structured show notes summarizing research papers."""
97
+ notes = "**Show Notes**\n\nIn today's episode:\n\n"
98
+ for i, item in enumerate(items):
99
+ notes += f"{i+1}. **{item['title']}**\n - {item['description']}\n - [Read More]({item['link']})\n\n"
100
+ return notes
101
+
102
+ async def generate_audio_parallel(conversation):
103
+ """Generates audio for the podcast in parallel."""
104
+ tasks = []
105
+ audio_files = []
106
+
107
+ for i, item in enumerate(conversation["conversation"]):
108
+ text = item["text"]
109
+ output_file = f"audio_{i}.mp3"
110
+ voice = "en-US-AndrewMultilingualNeural" if item["speaker"] == "Brian" else "en-US-AvaMultilingualNeural"
111
+
112
+ # Create async TTS tasks
113
+ tasks.append(edge_tts.Communicate(text=text, voice=voice).save(output_file))
114
+ audio_files.append(output_file)
115
+
116
+ # Run all tasks concurrently
117
+ await asyncio.gather(*tasks)
118
+ return audio_files
119
+
120
+ def merge_audio_files(audio_files, output_file="merged_audio.mp3"):
121
+ """Merges multiple audio files into one MP3 file."""
122
+ combined = AudioSegment.empty()
123
+ for file in audio_files:
124
+ audio = AudioSegment.from_file(file)
125
+ combined += audio
126
+ combined.export(output_file, format="mp3")
127
+ print(f"Merged audio saved to {output_file}")
128
+
129
+ def save_to_file(content, filename):
130
+ """Saves content to a file."""
131
+ with open(filename, "w") as file:
132
+ file.write(content)
133
+
134
+ def load_conversation():
135
+ """Loads conversation from file if exists."""
136
+ if os.path.exists(CONVERSATION_FILE):
137
+ with open(CONVERSATION_FILE, "r", encoding="utf-8") as file:
138
+ return json.load(file)
139
+ return None
140
+
141
+ async def generate_podcast():
142
+ """Generates podcast content (once per day or on force generate)."""
143
+ if os.path.exists(LAST_RUN_FILE):
144
+ with open(LAST_RUN_FILE, "r") as file:
145
+ last_run_date = file.read().strip()
146
+ if last_run_date == datetime.today().strftime("%Y-%m-%d"):
147
+ print("Podcast already generated today. Skipping...")
148
+ return
149
+
150
+ conversation = extract_conversation(daily_feed, items)
151
+ save_to_file(json.dumps(conversation, indent=2), CONVERSATION_FILE)
152
+ save_to_file(generate_show_notes(items), SHOW_NOTES_FILE)
153
+
154
+ print("Generating audio...")
155
+ audio_files = await generate_audio_parallel(conversation)
156
+ merge_audio_files(audio_files)
157
+
158
+ for file in audio_files:
159
+ os.remove(file)
160
+
161
+ with open(LAST_RUN_FILE, "w") as file:
162
+ file.write(datetime.today().strftime("%Y-%m-%d"))
163
+
164
+ print("Podcast and show notes generated successfully.")
165
+
166
+ # Generate daily podcast
167
+ asyncio.run(generate_podcast())
168
+
169
+ # Streamlit UI
170
+ st.set_page_config(page_title="AI Podcast", layout="wide")
171
+
172
+ conversation_data = load_conversation()
173
+ show_notes = "**No show notes available.**"
174
+ if os.path.exists(SHOW_NOTES_FILE):
175
+ with open(SHOW_NOTES_FILE, "r", encoding="utf-8") as f:
176
+ show_notes = f.read()
177
+
178
+ st.title("πŸŽ™οΈ Today's AI Podcast")
179
+
180
+ col1, col2 = st.columns([2, 1])
181
+
182
+ with col1:
183
+ st.subheader("🎧 Listen to the Podcast")
184
+ if os.path.exists(PODCAST_FILE):
185
+ audio_bytes = open(PODCAST_FILE, "rb").read()
186
+ st.audio(audio_bytes, format="audio/mp3")
187
+ else:
188
+ st.warning("No podcast available. Please generate an episode.")
189
+
190
+ st.subheader("πŸ“œ Show Notes")
191
+ st.markdown(show_notes)
192
+
193
+ with col2:
194
+ st.subheader("πŸ—¨οΈ Podcast Conversation")
195
+ if conversation_data:
196
+ for msg in conversation_data["conversation"]:
197
+ st.write(f"**{msg['speaker']}**: {msg['text']}")
198
+ else:
199
+ st.warning("No conversation data available.")
200
+
201
+ st.markdown("---")
202
+
203
+ if st.button("πŸ”„ Force Generate Podcast"):
204
+ asyncio.run(generate_podcast())
205
+ st.rerun()
206
+
207
+ st.markdown("πŸ“’ **Stay tuned for more AI research insights!**")