""" ๐ Anime Translator with Lip-Sync ================================= A Streamlit application that translates text between English and Hindi, converts it to speech, and generates a lip-synced anime avatar animation. """ import streamlit as st from pathlib import Path import tempfile import time import shutil import os import subprocess from shutil import which from typing import Tuple, Optional import base64 # Import utility modules from utils.translator import translate_text, detect_language from utils.tts_engine import synthesize_speech, get_audio_duration from utils.lipsync import generate_lipsync_gif from utils.speech_to_text import transcribe_audio, get_language_code from utils.avatar_manager import list_avatars, get_avatar_preview, ensure_sample_avatar # ============================================================================= # FFmpeg Configuration # ============================================================================= def configure_ffmpeg(): """Configure FFmpeg path for pydub on Windows.""" possible_paths = [ r"C:\ffmpeg\bin", r"C:\Program Files\ffmpeg\bin", r"C:\Program Files (x86)\ffmpeg\bin", os.path.expanduser("~\\ffmpeg\\bin"), r"C:\Users\Nishant Pratap\ffmpeg\bin", ] if which("ffmpeg") is not None: return True for path in possible_paths: ffmpeg_exe = os.path.join(path, "ffmpeg.exe") if os.path.exists(ffmpeg_exe): os.environ["PATH"] = path + os.pathsep + os.environ.get("PATH", "") try: from pydub import AudioSegment AudioSegment.converter = os.path.join(path, "ffmpeg.exe") AudioSegment.ffprobe = os.path.join(path, "ffprobe.exe") except: pass return True return False def check_ffmpeg_detailed(): """Check FFmpeg installation and return detailed status.""" status = { "ffmpeg_in_path": False, "ffmpeg_works": False, "ffprobe_works": False, "pydub_works": False, "error_message": None } ffmpeg_path = which("ffmpeg") status["ffmpeg_in_path"] = ffmpeg_path is not None try: result = subprocess.run( ["ffmpeg", "-version"], capture_output=True, text=True, timeout=5 ) status["ffmpeg_works"] = result.returncode == 0 except Exception as e: status["error_message"] = str(e) try: result = subprocess.run( ["ffprobe", "-version"], capture_output=True, text=True, timeout=5 ) status["ffprobe_works"] = result.returncode == 0 except Exception: pass try: from pydub import AudioSegment silence = AudioSegment.silent(duration=100) status["pydub_works"] = True except Exception as e: status["pydub_works"] = False if not status["error_message"]: status["error_message"] = str(e) return status ffmpeg_found = configure_ffmpeg() # ============================================================================= # Configuration # ============================================================================= AVATARS_DIR = Path("./avatars") TEMP_DIR = Path(tempfile.gettempdir()) / "anime_translator" AVATARS_DIR.mkdir(parents=True, exist_ok=True) TEMP_DIR.mkdir(parents=True, exist_ok=True) # Page configuration st.set_page_config( page_title="๐ Anime Translator", page_icon="๐", layout="wide", initial_sidebar_state="expanded" ) # Initialize session state for animation control if 'animation_playing' not in st.session_state: st.session_state.animation_playing = True if 'current_gif_path' not in st.session_state: st.session_state.current_gif_path = None # ============================================================================= # Custom CSS Styling - UPDATED WITH ANIMATION FIX # ============================================================================= st.markdown(""" """, unsafe_allow_html=True) # ============================================================================= # Helper Functions # ============================================================================= def cleanup_temp_files(older_than_sec: int = 3600) -> None: """Clean up old temporary files.""" now = time.time() try: for path in TEMP_DIR.iterdir(): try: if now - path.stat().st_mtime > older_than_sec: if path.is_file(): path.unlink() elif path.is_dir(): shutil.rmtree(path) except Exception: pass except Exception: pass def get_gif_first_frame(gif_path: str) -> Optional[str]: """Extract the first frame of a GIF as a static image.""" try: from PIL import Image import io with Image.open(gif_path) as img: # Get first frame img.seek(0) first_frame = img.copy() # Save to bytes buffer = io.BytesIO() first_frame.save(buffer, format='PNG') buffer.seek(0) # Convert to base64 img_base64 = base64.b64encode(buffer.getvalue()).decode() return img_base64 except Exception as e: print(f"Error extracting first frame: {e}") return None def display_animation_with_controls(gif_path: str, key_prefix: str = ""): """Display animation with play/pause/stop controls.""" if not gif_path or not os.path.exists(gif_path): st.info("โน๏ธ No animation available") return # Read GIF file with open(gif_path, "rb") as f: gif_data = f.read() gif_base64 = base64.b64encode(gif_data).decode() # Get first frame for static display first_frame_base64 = get_gif_first_frame(gif_path) # Animation state key state_key = f"{key_prefix}_playing" if state_key not in st.session_state: st.session_state[state_key] = True # Control buttons col1, col2, col3 = st.columns([1, 1, 1]) with col1: if st.button("โถ๏ธ Play", key=f"{key_prefix}_play", use_container_width=True): st.session_state[state_key] = True st.rerun() with col2: if st.button("โธ๏ธ Pause", key=f"{key_prefix}_pause", use_container_width=True): st.session_state[state_key] = False st.rerun() with col3: if st.button("โน๏ธ Stop", key=f"{key_prefix}_stop", use_container_width=True): st.session_state[state_key] = False st.rerun() # Display animation or static frame if st.session_state[state_key]: # Playing - show animated GIF st.markdown( f'''
โธ๏ธ Animation Paused
''', unsafe_allow_html=True ) else: st.info("Animation paused") # Download button st.download_button( label="๐ฅ Download Animation", data=gif_data, file_name="lipsync_animation.gif", mime="image/gif", key=f"{key_prefix}_download", use_container_width=True ) def process_translation_pipeline( text: str, source_lang: str, target_lang: str, avatar_name: str ) -> Tuple[str, Optional[str], Optional[str]]: """Main processing pipeline: translate, synthesize speech, generate animation.""" # Step 1: Translate text try: translated_text = translate_text(text, source_lang, target_lang) except Exception as e: raise Exception(f"Translation failed: {str(e)}") # Step 2: Synthesize speech try: audio_path = synthesize_speech(translated_text, target_lang, TEMP_DIR) except Exception as e: raise Exception(f"Speech synthesis failed: {str(e)}") # Step 3: Generate lip-sync animation gif_path = None try: gif_path = generate_lipsync_gif( avatar_name=avatar_name, audio_path=audio_path, avatars_dir=AVATARS_DIR, output_dir=TEMP_DIR, fps=12 ) except Exception as e: print(f"Animation generation warning: {str(e)}") gif_path = None return translated_text, audio_path, gif_path # ============================================================================= # Sidebar # ============================================================================= with st.sidebar: st.markdown("## โ๏ธ Settings") # Avatar selection st.markdown("### ๐ญ Avatar Selection") avatars = list_avatars(AVATARS_DIR) if avatars: selected_avatar = st.selectbox( "Choose your avatar", options=avatars, index=0, help="Select an anime avatar for lip-sync animation" ) preview = get_avatar_preview(selected_avatar, AVATARS_DIR) if preview: st.image(preview, caption=f"Preview: {selected_avatar}", use_container_width=True) else: st.warning("No avatars found. Creating sample avatar...") ensure_sample_avatar(AVATARS_DIR) selected_avatar = "sample" st.rerun() st.markdown("---") # Language settings st.markdown("### ๐ Language Settings") source_language = st.selectbox( "Source Language", options=["auto", "en", "hi"], format_func=lambda x: {"auto": "๐ Auto-detect", "en": "๐ฌ๐ง English", "hi": "๐ฎ๐ณ Hindi"}[x], index=0 ) target_language = st.selectbox( "Target Language", options=["en", "hi"], format_func=lambda x: {"en": "๐ฌ๐ง English", "hi": "๐ฎ๐ณ Hindi"}[x], index=1 ) st.markdown("---") # Animation settings st.markdown("### ๐ฌ Animation Settings") animation_size = st.slider( "Animation Size", min_value=200, max_value=500, value=350, step=50, help="Adjust the display size of the animation" ) auto_play = st.checkbox("Auto-play animation", value=True) st.markdown("---") # System status st.markdown("### ๐ง System Status") ffmpeg_status = check_ffmpeg_detailed() if ffmpeg_status["ffmpeg_works"]: st.success("โ FFmpeg: Working") else: st.error("โ FFmpeg: Not working") if ffmpeg_status["pydub_works"]: st.success("โ Pydub: Working") else: st.warning("โ ๏ธ Pydub: Limited (fallback mode)") if ffmpeg_status["error_message"]: with st.expander("๐ Error Details"): st.code(ffmpeg_status["error_message"]) st.markdown(""" **To fix FFmpeg:** ```bash conda install -c conda-forge ffmpeg ``` Or download from: https://www.gyan.dev/ffmpeg/builds/ """) st.markdown("---") # Info section st.markdown("### โน๏ธ About") st.markdown(""" Translate text between English and Hindi with lip-synced avatar animation. **Features:** - ๐ค Voice input - ๐ Auto detection - ๐ฃ๏ธ Text-to-speech - ๐ฌ Lip-sync animation """) if st.button("๐งน Clear Temp Files"): cleanup_temp_files(older_than_sec=0) st.success("Cleared!") # ============================================================================= # Main Content # ============================================================================= st.markdown('' 'Translate โข Speak โข Animate
', unsafe_allow_html=True ) # Tabs tab1, tab2 = st.tabs(["๐ Text Input", "๐ค Voice Input"]) # ============================================================================= # Tab 1: Text Input # ============================================================================= with tab1: col1, col2 = st.columns([1, 1]) with col1: st.markdown("### ๐ Enter Your Text") text_input = st.text_area( "Type or paste your text here", height=150, placeholder="Enter text in English or Hindi...\nเคเคฆเคพเคนเคฐเคฃ: เคจเคฎเคธเฅเคคเฅ, เคเคช เคเฅเคธเฅ เคนเฅเค?\nExample: Hello, how are you?", key="text_input" ) if text_input: detected = detect_language(text_input) st.markdown( f'{result["translated"]}
' f'{result["translated"]}
' f'Made By Praveen