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 | |
""") |