Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import feedparser | |
| import requests | |
| import json | |
| import os | |
| from bs4 import BeautifulSoup | |
| from transformers import MarianMTModel, MarianTokenizer | |
| import whisper | |
| from streamlit_audio_recorder import st_audiorec # Make sure this library is installed | |
| # Configure the Streamlit page | |
| st.set_page_config(page_title="📍 BharatPulse - Local News & Sentiment", layout="wide") | |
| # --- User Authentication Functions --- | |
| # IMPORTANT: For production, this 'users.json' file will not persist across deployments | |
| # on cloud platforms like Streamlit Community Cloud. Consider using a database (e.g., Firebase, PostgreSQL) | |
| # for persistent user data. | |
| USERS_FILE = "users.json" | |
| def load_users(): | |
| """Loads user data from the JSON file.""" | |
| if not os.path.exists(USERS_FILE): | |
| return {} | |
| try: | |
| with open(USERS_FILE, "r") as f: | |
| return json.load(f) | |
| except json.JSONDecodeError: | |
| # Handle case where file might be empty or corrupted | |
| st.error("Error loading user data. Initializing empty user database.") | |
| return {} | |
| def save_users(users): | |
| """Saves user data to the JSON file.""" | |
| with open(USERS_FILE, "w") as f: | |
| json.dump(users, f, indent=2) | |
| def register_user(username, email, password, location): | |
| """Registers a new user.""" | |
| users = load_users() | |
| if email in users: | |
| return False, "User already exists with this email." | |
| users[email] = { | |
| "username": username, | |
| "email": email, | |
| "password": password, # In a real app, hash passwords! | |
| "location": location | |
| } | |
| save_users(users) | |
| return True, "Registration successful. You can now log in." | |
| def login_user(email, password): | |
| """Logs in an existing user.""" | |
| users = load_users() | |
| user = users.get(email) | |
| if user and user["password"] == password: # In a real app, compare hashed passwords! | |
| return True, user | |
| return False, "Invalid email or password." | |
| # --- Whisper Model for Audio Transcription --- | |
| # Caches the model to avoid reloading on rerun | |
| def load_whisper_model(): | |
| """Loads the Whisper base model.""" | |
| st.info("Loading Whisper model (this may take a moment)...") | |
| model = whisper.load_model("base") | |
| st.success("Whisper model loaded!") | |
| return model | |
| def transcribe_audio(audio_bytes): | |
| """Transcribes audio bytes to text using Whisper.""" | |
| # Save audio to a temporary file | |
| path = "/tmp/voice.wav" | |
| try: | |
| with open(path, "wb") as f: | |
| f.write(audio_bytes) | |
| model = load_whisper_model() | |
| # Specify language for better accuracy if known | |
| result = model.transcribe(path, language="te", fp16=False) # fp16=False for CPU compatibility | |
| return result["text"] | |
| except Exception as e: | |
| st.error(f"Error during audio transcription: {e}") | |
| return "" | |
| finally: | |
| # Clean up the temporary file | |
| if os.path.exists(path): | |
| os.remove(path) | |
| # --- MarianMT Model for Translation --- | |
| # Caches the model to avoid reloading on rerun | |
| def load_translation_model(): | |
| """Loads the MarianMT English-Telugu translation model.""" | |
| st.info("Loading translation model (this may take a moment)...") | |
| model_name = "Helsinki-NLP/opus-mt-en-te" | |
| tokenizer = MarianTokenizer.from_pretrained(model_name) | |
| model = MarianMTModel.from_pretrained(model_name) | |
| st.success("Translation model loaded!") | |
| return tokenizer, model | |
| def translate_to_telugu(text): | |
| """Translates English text to Telugu.""" | |
| try: | |
| tokenizer, model = load_translation_model() | |
| # Prepare input for the model, ensuring truncation for long texts | |
| tokens = tokenizer.prepare_seq2seq_batch([text], return_tensors="pt", truncation=True, max_length=512) | |
| translated = model.generate(**tokens) | |
| return tokenizer.decode(translated[0], skip_special_tokens=True) | |
| except Exception as e: | |
| st.error(f"Error during translation: {e}") | |
| return "Translation failed." | |
| # --- Article Content Extraction --- | |
| def extract_article_content(url): | |
| """Extracts the first few paragraphs from an article URL.""" | |
| try: | |
| # Add headers to mimic a browser, as some sites block direct requests | |
| headers = { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' | |
| } | |
| response = requests.get(url, timeout=10, headers=headers) # Increased timeout | |
| response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx) | |
| soup = BeautifulSoup(response.text, "html.parser") | |
| paragraphs = soup.find_all('p') | |
| # Join the text of the first 5 paragraphs, or fewer if not available | |
| content = ' '.join([p.get_text() for p in paragraphs[:5]]) | |
| if not content: # If no paragraphs found, try to get text from body | |
| content = soup.body.get_text(separator=' ', strip=True)[:1000] # Get first 1000 chars | |
| return content if content else "Could not extract article content." | |
| except requests.exceptions.RequestException as e: | |
| return f"Network or HTTP error: {e}" | |
| except Exception as e: | |
| return f"Error extracting content: {e}" | |
| # --- Authentication UI --- | |
| # Initialize session state for login status | |
| if "logged_in" not in st.session_state: | |
| st.session_state.logged_in = False | |
| if "user" not in st.session_state: | |
| st.session_state.user = None | |
| if not st.session_state.logged_in: | |
| st.title("🔐 Login to BharatPulse") | |
| tab1, tab2 = st.tabs(["🔑 Login", "📝 Register"]) | |
| with tab1: | |
| st.subheader("Existing User Login") | |
| email = st.text_input("Email", key="login_email") | |
| password = st.text_input("Password", type="password", key="login_password") | |
| if st.button("Login", key="login_button"): | |
| success, user_or_msg = login_user(email, password) | |
| if success: | |
| st.session_state.logged_in = True | |
| st.session_state.user = user_or_msg | |
| st.success(f"Welcome, {st.session_state.user['username']}!") | |
| st.rerun() # Rerun to switch to the main app view | |
| else: | |
| st.error(user_or_msg) | |
| with tab2: | |
| st.subheader("New User Registration") | |
| new_username = st.text_input("Username", key="register_username") | |
| new_email = st.text_input("New Email", key="register_email") | |
| new_password = st.text_input("New Password", type="password", key="register_password") | |
| new_location = st.text_input("Location (e.g., Hyderabad)", key="register_location") | |
| if st.button("Register", key="register_button"): | |
| if new_username and new_email and new_password and new_location: | |
| success, message = register_user(new_username, new_email, new_password, new_location) | |
| if success: | |
| st.success(message) | |
| else: | |
| st.error(message) | |
| else: | |
| st.warning("Please fill in all registration fields.") | |
| st.stop() # Stop execution if not logged in | |
| # --- Main News UI (only runs if logged in) --- | |
| st.sidebar.header("User Info") | |
| st.sidebar.success(f"Logged in as: {st.session_state.user['username']}") | |
| st.sidebar.info(f"Your registered location: {st.session_state.user['location']}") | |
| if st.sidebar.button("🚪 Logout"): | |
| st.session_state.logged_in = False | |
| st.session_state.user = None | |
| st.rerun() # Rerun to go back to login screen | |
| st.title("📍 BharatPulse - Local News & Sentiment") | |
| # Define RSS feeds | |
| # You can expand this dictionary with more RSS feeds for different regions/newspapers | |
| feeds = { | |
| "Sakshi - Medak": "https://www.sakshi.com/rss/district-news/medak.xml", | |
| "Eenadu - AP": "https://www.eenadu.net/rss/andhra-pradesh.xml", | |
| # Add more feeds here, e.g., "Eenadu - Telangana": "https://www.eenadu.net/rss/telangana.xml" | |
| } | |
| tabs = st.tabs(["📰 Headlines", "🔍 Search by City", "🎤 Voice Search"]) | |
| # --- 📰 Headlines Tab --- | |
| with tabs[0]: | |
| st.header("📰 Latest Local Telugu News Headlines") | |
| for name, url in feeds.items(): | |
| st.subheader(name) | |
| try: | |
| feed = feedparser.parse(url) | |
| if not feed.entries: | |
| st.info(f"No entries found for {name}.") | |
| continue | |
| for entry in feed.entries[:3]: # Display top 3 articles per feed | |
| st.markdown(f"### [{entry.title}]({entry.link})") # Make title a clickable link | |
| st.write(f"Published: {entry.published}") | |
| # Extract and summarize article content | |
| summary = extract_article_content(entry.link) | |
| st.markdown(f"**English Summary:** {summary}") | |
| # Translate summary to Telugu | |
| telugu_summary = translate_to_telugu(summary) | |
| st.markdown(f"**తెలుగు అనువాదం:** {telugu_summary}") | |
| st.markdown("---") | |
| except Exception as e: | |
| st.error(f"Could not fetch news from {name}: {e}") | |
| # --- 🔍 Search by City Tab --- | |
| with tabs[1]: | |
| st.header("🔎 Search News by City / District") | |
| # Pre-fill with user's registered location if available | |
| default_city = st.session_state.user['location'] if st.session_state.user else "" | |
| city_search_input = st.text_input("Enter your city/district name (e.g., Medak, Hyderabad)", value=default_city) | |
| if city_search_input: | |
| st.success(f"Searching news for: **{city_search_input}**") | |
| found_news = False | |
| for name, url in feeds.items(): | |
| # Basic matching: checks if the city name is in the feed's name (case-insensitive) | |
| if city_search_input.lower() in name.lower(): | |
| st.subheader(f"News from {name}") | |
| try: | |
| feed = feedparser.parse(url) | |
| if not feed.entries: | |
| st.info(f"No entries found for {name} matching '{city_search_input}'.") | |
| continue | |
| for entry in feed.entries[:2]: # Display top 2 articles per matching feed | |
| st.markdown(f"**🗞️ [{entry.title}]({entry.link})**") | |
| summary = extract_article_content(entry.link) | |
| telugu_summary = translate_to_telugu(summary) | |
| st.markdown(f"**తెలుగు అనువాదం:** {telugu_summary}") | |
| st.markdown("---") | |
| found_news = True | |
| except Exception as e: | |
| st.error(f"Could not fetch news from {name}: {e}") | |
| if not found_news: | |
| st.info(f"No news found for '{city_search_input}' in the available feeds. Try a different city or check our 'Headlines' tab.") | |
| # --- 🎤 Voice Search Tab --- | |
| with tabs[2]: | |
| st.header("🎤 Search by Telugu Voice") | |
| st.info("Click 'Record' to start, 'Stop' when done. Speak the city/district name in Telugu.") | |
| # Use the audio recorder component | |
| audio_bytes = st_audiorec() | |
| if audio_bytes: | |
| with st.spinner("Transcribing audio..."): | |
| transcribed_text = transcribe_audio(audio_bytes) | |
| if transcribed_text: | |
| st.success(f"Voice Input Recognized: **{transcribed_text}**") | |
| city_from_voice = transcribed_text.strip() # Use the transcribed text as the city | |
| st.subheader(f"Searching news for: **{city_from_voice}**") | |
| found_news_voice = False | |
| for name, url in feeds.items(): | |
| # Basic matching: checks if the transcribed city name is in the feed's name | |
| if city_from_voice.lower() in name.lower(): | |
| st.subheader(f"News from {name}") | |
| try: | |
| feed = feedparser.parse(url) | |
| if not feed.entries: | |
| st.info(f"No entries found for {name} matching '{city_from_voice}'.") | |
| continue | |
| for entry in feed.entries[:2]: # Display top 2 articles per matching feed | |
| st.markdown(f"**🗞️ [{entry.title}]({entry.link})**") | |
| summary = extract_article_content(entry.link) | |
| telugu_summary = translate_to_telugu(summary) | |
| st.markdown(f"**తెలుగు అనువాదం:** {telugu_summary}") | |
| st.markdown("---") | |
| found_news_voice = True | |
| except Exception as e: | |
| st.error(f"Could not fetch news from {name}: {e}") | |
| if not found_news_voice: | |
| st.info(f"No news found for '{city_from_voice}' in the available feeds. Try speaking clearly or check our 'Headlines' tab.") | |
| else: | |
| st.warning("Could not transcribe audio. Please try again.") | |