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 --- @st.cache_resource # 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 --- @st.cache_resource # 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.")