Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| from PIL import Image | |
| import numpy as np | |
| import io | |
| import base64 | |
| import time | |
| import os | |
| # Disable Streamlit config file creation - Critical for Hugging Face Spaces | |
| os.environ["STREAMLIT_GLOBAL_DEVELOPMENT_MODE"] = "false" | |
| os.environ["STREAMLIT_GLOBAL_RECURSION"] = "false" | |
| # Page setup - Vertical layout for 9:16 aspect | |
| st.set_page_config( | |
| page_title="Free Hedra Clone", | |
| page_icon="🤖", | |
| layout="centered", | |
| initial_sidebar_state="expanded" | |
| ) | |
| st.title("🎭 Free AI Avatar Generator") | |
| st.subheader("Audio-Driven Speaking Avatar (9:16 Portrait)") | |
| # Initialize session state | |
| if "frame" not in st.session_state: | |
| st.session_state.frame = 0 | |
| if "avatar_img" not in st.session_state: | |
| st.session_state.avatar_img = None | |
| if "audio_data" not in st.session_state: | |
| st.session_state.audio_data = None | |
| if "audio_duration" not in st.session_state: | |
| st.session_state.audio_duration = 5 | |
| if "mime_type" not in st.session_state: | |
| st.session_state.mime_type = "audio/mp3" | |
| # Create default 9:16 avatar | |
| def create_default_avatar(): | |
| width, height = 720, 1280 | |
| img = Image.new('RGB', (width, height), color=(73, 109, 137)) | |
| return img | |
| # Sidebar configuration | |
| with st.sidebar: | |
| st.header("🎛️ Configuration") | |
| # Audio file upload | |
| audio_file = st.file_uploader( | |
| "Upload Audio File:", | |
| type=["mp3", "wav"], | |
| help="Upload an audio file (MP3 or WAV)" | |
| ) | |
| # Process audio immediately after upload | |
| if audio_file: | |
| st.session_state.audio_data = audio_file.read() | |
| if audio_file.name.endswith('.wav'): | |
| st.session_state.mime_type = "audio/wav" | |
| else: | |
| st.session_state.mime_type = "audio/mp3" | |
| # Set fixed duration since we can't reliably calculate it | |
| st.session_state.audio_duration = 5 | |
| st.info("Audio duration set to 5 seconds (fixed)") | |
| # Avatar image upload | |
| uploaded_image = st.file_uploader( | |
| "Upload Avatar Image:", | |
| type=["png", "jpg", "jpeg"] | |
| ) | |
| # Process image immediately after upload | |
| if uploaded_image: | |
| try: | |
| img_data = uploaded_image.read() | |
| st.session_state.avatar_img = Image.open(io.BytesIO(img_data)) | |
| except: | |
| st.error("Error processing image") | |
| # Animation options | |
| st.markdown("---") | |
| st.markdown("### Animation Options") | |
| pulse_intensity = st.slider("Pulse Intensity", 0.1, 1.0, 0.3, 0.1) | |
| animation_speed = st.slider("Animation Speed", 0.1, 2.0, 0.5, 0.1) | |
| mouth_intensity = st.slider("Mouth Movement", 0.5, 2.0, 1.2, 0.1) | |
| custom_duration = st.slider("Animation Duration (seconds)", 1, 30, 5, 1) | |
| # Create speaking animation | |
| def animate_avatar(image, intensity=0.3, anim_speed=0.5, mouth_factor=1.2): | |
| try: | |
| # Convert to RGB if needed | |
| if image.mode != 'RGB': | |
| image = image.convert('RGB') | |
| img_array = np.array(image) | |
| height, width, _ = img_array.shape | |
| # Create pulsation effect | |
| pulse = 1.0 + intensity * np.sin(st.session_state.frame * anim_speed) | |
| # Apply stronger effect to bottom half | |
| for y in range(height): | |
| position_factor = y / height | |
| if position_factor > 0.6: # Mouth region | |
| region_intensity = intensity * mouth_factor | |
| region_pulse = 1.0 + region_intensity * np.sin(st.session_state.frame * anim_speed * 1.5) | |
| else: | |
| region_pulse = pulse | |
| img_array[y] = np.clip(img_array[y] * region_pulse, 0, 255).astype(np.uint8) | |
| return Image.fromarray(img_array) | |
| except Exception as e: | |
| st.error(f"Animation error: {str(e)}") | |
| return image | |
| # Audio player component | |
| def audio_player(audio_bytes, mime_type="audio/mp3"): | |
| audio_base64 = base64.b64encode(audio_bytes).decode() | |
| audio_html = f""" | |
| <audio controls autoplay style="width: 100%"> | |
| <source src="data:{mime_type};base64,{audio_base64}" type="{mime_type}"> | |
| </audio> | |
| """ | |
| st.markdown(audio_html, unsafe_allow_html=True) | |
| # Create default avatar if needed | |
| if st.session_state.avatar_img is None: | |
| st.session_state.avatar_img = create_default_avatar() | |
| # Display avatar preview | |
| st.subheader("Avatar Preview") | |
| st.image( | |
| st.session_state.avatar_img, | |
| caption="Your Avatar", | |
| use_column_width=True | |
| ) | |
| # Generate button | |
| if st.session_state.audio_data: | |
| if st.button("Generate Speaking Avatar", type="primary", use_container_width=True): | |
| # Create animation container | |
| st.subheader("Speaking Avatar") | |
| animation_placeholder = st.empty() | |
| # Play audio | |
| st.subheader("Audio Player") | |
| audio_player(st.session_state.audio_data, st.session_state.mime_type) | |
| # Create animation | |
| st.subheader("Avatar Animation") | |
| # Animate for custom duration | |
| start_time = time.time() | |
| frames = 0 | |
| while time.time() - start_time < custom_duration: | |
| try: | |
| animated_img = animate_avatar( | |
| st.session_state.avatar_img, | |
| pulse_intensity, | |
| animation_speed, | |
| mouth_intensity | |
| ) | |
| animation_placeholder.image(animated_img, use_column_width=True) | |
| st.session_state.frame += 1 | |
| frames += 1 | |
| time.sleep(0.1) | |
| except Exception as e: | |
| st.error(f"Error during animation: {str(e)}") | |
| break | |
| # Download audio button | |
| st.subheader("Download Options") | |
| st.download_button( | |
| label="Download Audio", | |
| data=st.session_state.audio_data, | |
| file_name=f"avatar_audio.{'mp3' if st.session_state.mime_type == 'audio/mp3' else 'wav'}", | |
| mime=st.session_state.mime_type, | |
| use_container_width=True | |
| ) | |
| else: | |
| st.warning("Please upload an audio file to generate the speaking avatar") | |
| # Footer | |
| st.markdown("---") | |
| st.caption(""" | |
| **Free Hedra Clone** - Audio-driven avatar animation | |
| All processing done in memory - No file system writes | |
| """) |