Spaces:
Sleeping
Sleeping
| import requests | |
| import json | |
| import os | |
| import xml.etree.ElementTree as ET | |
| import openai | |
| import edge_tts | |
| from pydub import AudioSegment | |
| import re | |
| import time | |
| import asyncio | |
| import streamlit as st | |
| from datetime import datetime | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| PODCAST_FILE = "merged_audio.mp3" | |
| SHOW_NOTES_FILE = "show_notes.txt" | |
| CONVERSATION_FILE = "conversation.json" | |
| LAST_RUN_FILE = "last_run.txt" | |
| feed_url = "http://papers.takara.ai/api/feed" | |
| response = requests.get(feed_url) | |
| tree = ET.ElementTree(ET.fromstring(response.text)) | |
| daily_feed = "" | |
| items = [] | |
| for item in tree.iter("item"): | |
| title = item.find("title").text | |
| link = item.find("link").text | |
| description = item.find("description").text | |
| items.append({"title": title, "link": link, "description": description}) | |
| daily_feed += f"Title: {title.strip()}\nDescription: {description}\n\n" | |
| client = openai.Client( | |
| api_key=os.getenv("DEEPINFRA_API"), | |
| base_url="https://api.deepinfra.com/v1/openai", | |
| ) | |
| def build_prompt(text, items): | |
| """Generates a well-balanced podcast conversation covering all research papers, ensuring correct JSON format.""" | |
| paper_summaries = "\n".join( | |
| [f"- {item['title']} ({item['link']}): {item['description']}" for item in items] | |
| ) | |
| template = """ | |
| { | |
| "conversation": [ | |
| {"speaker": "Brian", "text": ""}, | |
| {"speaker": "Jenny", "text": ""} | |
| ] | |
| } | |
| """ | |
| return ( | |
| f"ποΈ Welcome to Daily Papers! Today, we're diving into the latest AI research in an engaging and " | |
| f"informative discussion. The goal is to make it a **medium-length podcast** thatβs **engaging, natural, and insightful** while covering " | |
| f"the key points of each paper.\n\n" | |
| f"Here are today's research papers:\n{paper_summaries}\n\n" | |
| f"Convert this into a **conversational podcast-style discussion** between two experts, Brian and Jenny. " | |
| f"Ensure the conversation flows naturally, using a mix of **insightful analysis, casual phrasing, and occasional filler words** like 'uhm' and 'you know' " | |
| f"to keep it realistic. The tone should be engaging yet professional, making it interesting for the audience.\n\n" | |
| f"Each research paper should be **discussed meaningfully**, but avoid dragging the conversation too long. " | |
| f"Focus on key insights and practical takeaways. Keep the pacing dynamic and interactive.\n\n" | |
| f"Please return the conversation in **this exact JSON format**:\n{template}" | |
| ) | |
| def extract_conversation(text, items, max_retries=3): | |
| """Extracts podcast conversation from OpenAI API with retries.""" | |
| for attempt in range(1, max_retries + 1): | |
| try: | |
| print(f"Attempt {attempt} to generate conversation...") | |
| chat_completion = client.chat.completions.create( | |
| messages=[{"role": "user", "content": build_prompt(text, items)}], | |
| model="meta-llama/Llama-3.3-70B-Instruct-Turbo", | |
| temperature=0.7, | |
| max_tokens=4096, | |
| ) | |
| pattern = r"\{(?:[^{}]|(?:\{[^{}]*\}))*\}" | |
| json_match = re.search(pattern, chat_completion.choices[0].message.content) | |
| if json_match: | |
| return json.loads(json_match.group()) | |
| raise ValueError("No valid JSON found in response") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| if attempt < max_retries: | |
| time.sleep(2) | |
| else: | |
| raise RuntimeError(f"Failed after {max_retries} attempts.") | |
| def generate_show_notes(items): | |
| """Creates structured show notes summarizing research papers.""" | |
| notes = "**Show Notes**\n\nIn today's episode:\n\n" | |
| for i, item in enumerate(items): | |
| notes += f"{i+1}. **{item['title']}**\n - {item['description']}\n - [Read More]({item['link']})\n\n" | |
| return notes | |
| async def generate_audio_parallel(conversation): | |
| """Generates audio for the podcast in parallel.""" | |
| tasks = [] | |
| audio_files = [] | |
| for i, item in enumerate(conversation["conversation"]): | |
| text = item["text"] | |
| output_file = f"audio_{i}.mp3" | |
| voice = "en-GB-RyanNeural" if item["speaker"] == "Brian" else "en-US-AvaMultilingualNeural" | |
| tasks.append(edge_tts.Communicate(text=text, voice=voice).save(output_file)) | |
| audio_files.append(output_file) | |
| await asyncio.gather(*tasks) | |
| return audio_files | |
| def merge_audio_files(audio_files, output_file="merged_audio.mp3"): | |
| """Merges multiple audio files into one MP3 file.""" | |
| combined = AudioSegment.empty() | |
| for file in audio_files: | |
| audio = AudioSegment.from_file(file) | |
| combined += audio | |
| combined.export(output_file, format="mp3") | |
| print(f"Merged audio saved to {output_file}") | |
| def save_to_file(content, filename): | |
| """Saves content to a file.""" | |
| with open(filename, "w") as file: | |
| file.write(content) | |
| def load_conversation(): | |
| """Loads conversation from file if exists.""" | |
| if os.path.exists(CONVERSATION_FILE): | |
| with open(CONVERSATION_FILE, "r", encoding="utf-8") as file: | |
| return json.load(file) | |
| return None | |
| async def generate_podcast(): | |
| """Generates podcast content (once per day or on force generate).""" | |
| if os.path.exists(LAST_RUN_FILE): | |
| with open(LAST_RUN_FILE, "r") as file: | |
| last_run_date = file.read().strip() | |
| if last_run_date == datetime.today().strftime("%Y-%m-%d"): | |
| print("Podcast already generated today. Skipping...") | |
| return | |
| conversation = extract_conversation(daily_feed, items) | |
| save_to_file(json.dumps(conversation, indent=2), CONVERSATION_FILE) | |
| save_to_file(generate_show_notes(items), SHOW_NOTES_FILE) | |
| print("Generating audio...") | |
| audio_files = await generate_audio_parallel(conversation) | |
| merge_audio_files(audio_files) | |
| for file in audio_files: | |
| os.remove(file) | |
| with open(LAST_RUN_FILE, "w") as file: | |
| file.write(datetime.today().strftime("%Y-%m-%d")) | |
| print("Podcast and show notes generated successfully.") | |
| asyncio.run(generate_podcast()) | |
| st.set_page_config(page_title="Daily Papers Podcast", page_icon="ποΈ", layout="wide") | |
| st.title("ποΈ Today's Daily Papers Podcast") | |
| st.subheader("Your Daily AI Research Insights - Engaging & Informative") | |
| col1, col2 = st.columns([0.2, 0.8]) | |
| with col1: | |
| st.image("Logo.png", width=120) # Ensure logo.png is in the working directory | |
| with col2: | |
| st.markdown( | |
| """ | |
| **Powered by:** | |
| π [HF Daily Papers Feeds](https://github.com/404missinglink/HF-Daily-Papers-Feeds) | |
| π [TLDR Takara AI](https://tldr.takara.ai/) | |
| π [Takara AI Papers Feed](http://papers.takara.ai/api/feed) | |
| """ | |
| ) | |
| # Styling Divider | |
| st.markdown("---") | |
| conversation_data = load_conversation() | |
| show_notes = "**No show notes available.**" | |
| if os.path.exists(SHOW_NOTES_FILE): | |
| with open(SHOW_NOTES_FILE, "r", encoding="utf-8") as f: | |
| show_notes = f.read() | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.subheader("π§ Listen to the Podcast") | |
| if os.path.exists(PODCAST_FILE): | |
| audio_bytes = open(PODCAST_FILE, "rb").read() | |
| st.audio(audio_bytes, format="audio/mp3") | |
| else: | |
| st.warning("No podcast available. Please generate an episode.") | |
| st.subheader("π Show Notes") | |
| st.markdown(show_notes) | |
| with col2: | |
| st.subheader("π¨οΈ Podcast Conversation") | |
| if conversation_data: | |
| for msg in conversation_data["conversation"]: | |
| st.write(f"**{msg['speaker']}**: {msg['text']}") | |
| else: | |
| st.warning("No conversation data available.") | |
| st.markdown("---") | |
| if st.button("π Force Generate Podcast"): | |
| asyncio.run(generate_podcast()) | |
| st.rerun() | |
| st.markdown("π’ **Stay tuned for more AI research insights!**") | |