"""
Hand2Voice: AI-Powered Sign Language Assistant
----------------------------------------------
Version: 11.0.0 (Footer Fix)
Author: Lovnish Verma
Organization: Government Research / NIELIT
Date: 2025-12-24
Description:
A production-ready accessibility tool that translates hand gestures into spoken audio.
Fixes in v11.0:
- Added bottom padding to main container so Footer doesn't overlap text.
- Enforced Z-Index layering to keep alerts visible.
"""
import streamlit as st
import numpy as np
from PIL import Image, ImageOps
import os
import base64
import streamlit.components.v1 as components
# --- Local Modules ---
from gesture_classifier import classify_gesture
from tts import speak
# --- Configuration ---
PAGE_TITLE = "Hand2Voice"
PAGE_ICON = "🤟"
LAYOUT = "wide"
LOGO_FILE = "NIELIT-LOGO.png"
GIF_FILE = "hand_animation.gif"
GIF_URL = "https://mediapipe.dev/images/mobile/hand_tracking_3d_android_gpu.gif"
AUTHOR_NAME = "Lovnish Verma"
COPYRIGHT_YEAR = "2026"
# --- 1. THE CONCRETE CSS (Layout & Design System) ---
STABILIZATION_CSS = """
"""
# --- 2. ASSET MANAGEMENT ---
@st.cache_data
def load_image(path):
"""Loads image resources efficiently."""
if os.path.exists(path):
return Image.open(path)
return None
def inject_css():
st.markdown(STABILIZATION_CSS, unsafe_allow_html=True)
# --- 3. INVISIBLE AUDIO ENGINE ---
def play_audio_js(file_path):
"""Executes audio playback via pure JavaScript."""
try:
with open(file_path, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode()
js = f"""
"""
components.html(js, height=0, width=0)
except Exception as e:
print(f"Audio Error: {e}")
# --- 4. UI COMPONENTS ---
def render_status_box(text, color, icon):
"""Renders a consistent, professional status indicator."""
st.markdown(f"""
{icon}
{text}
""", unsafe_allow_html=True)
def render_sidebar():
"""Renders the navigation sidebar."""
with st.sidebar:
logo = load_image(LOGO_FILE)
if logo:
st.image(logo, use_container_width=True)
if os.path.exists(GIF_FILE):
st.image(GIF_FILE, caption="MediaPipe Hand Tracking")
else:
st.image(GIF_URL, caption="MediaPipe Hand Tracking (Web)")
st.divider()
st.subheader("📖 How to Use")
st.markdown("""
1. **Select Input:** Choose 'Camera' or 'Upload'.
2. **Capture:** Click button to capture.
3. **Wait:** AI analyzes hand.
4. **Listen:** Hear the result.
""")
st.divider()
st.subheader("✌️ Supported Signs")
st.markdown("""
| Gesture | Meaning |
| :--- | :--- |
| ✋ **Palm** | Hello |
| ✌️ **V-Sign** | Peace / Victory |
| ✊ **Fist** | No / Stop |
| 🤘 **Rock** | Rock On |
| 👌 **OK** | Okay / Perfect |
| 👆 **Index** | Yes / Pointing |
""")
st.divider()
st.info(f"Designed by **{AUTHOR_NAME}**")
def main():
st.set_page_config(page_title=PAGE_TITLE, page_icon=PAGE_ICON, layout=LAYOUT)
inject_css()
render_sidebar()
# --- MAIN HEADER ---
st.title(f"{PAGE_ICON} {PAGE_TITLE}")
st.markdown(f"### **AI Sign Language Assistant**")
st.markdown("Translating silence into sound using Computer Vision.")
col1, col2 = st.columns([1, 1], gap="large")
# --- COLUMN 1: INPUT ---
with col1:
st.subheader("1. Input Source")
tab_cam, tab_up = st.tabs(["📷 Camera", "📂 Upload Image"])
input_buffer = None
with tab_cam:
input_buffer = st.camera_input("Capture Gesture")
with tab_up:
upl = st.file_uploader("Choose a file", type=["jpg", "png", "jpeg"])
if upl: input_buffer = upl
# --- COLUMN 2: OUTPUT ---
with col2:
st.subheader("2. AI Analysis")
result_container = st.container()
# --- PROCESSING LOGIC ---
if input_buffer:
try:
# 1. Load Image
img = Image.open(input_buffer).convert("RGB")
# 2. Resize
img = ImageOps.fit(img, (640, 480), method=Image.Resampling.LANCZOS)
frame = np.array(img)
# 3. Predict
gesture, annotated_img = classify_gesture(frame)
# 4. Render Result
with result_container:
st.image(annotated_img, caption="Computer Vision View", use_container_width=True)
if gesture is None:
render_status_box("No hand detected. Please try again.", "#f8d7da", "⚠️")
elif gesture == "UNKNOWN":
render_status_box("Gesture not recognized.", "#fff3cd", "❓")
else:
render_status_box(f"Detected: {gesture}", "#d4edda", "✅")
# 5. Speak Result
if "last_spoken" not in st.session_state or st.session_state.last_spoken != gesture:
audio_file = speak(gesture)
st.session_state.last_spoken = gesture
play_audio_js(audio_file)
except Exception as e:
st.error(f"System Error: {e}")
else:
# --- EMPTY STATE ---
with result_container:
placeholder = Image.new("RGB", (640, 480), (240, 240, 240))
st.image(placeholder, caption="Waiting for input...", use_container_width=True)
render_status_box("Waiting for gesture...", "#e2e3e5", "⏳")
# --- COPYRIGHT FOOTER ---
st.markdown(f"""
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()