hwonder's picture
Add 3D model (GLTF/GLB) support for Text to Image
b99fda7
"""
Event Handlers
Handlers for all Gradio UI events.
Connects UI to services with error handling.
"""
import asyncio
from typing import Optional, Tuple
from ..services.music import MusicService
from ..services.image import ImageService
from ..services.video import VideoService
from ..api.client import StackNetClient
def format_error(error: Exception) -> str:
"""Format exception as user-friendly message."""
msg = str(error)
# Translate common errors
if "timeout" in msg.lower():
return "The operation timed out. Please try again with a simpler prompt."
if "network" in msg.lower() or "connection" in msg.lower():
return "Network error. Please check your connection and try again."
if "rate limit" in msg.lower() or "429" in msg.lower():
return "Too many requests. Please wait a moment and try again."
return f"Error: {msg}"
class Handlers:
"""
Collection of event handlers for the Gradio UI.
All handlers hide API complexity and provide clean feedback.
"""
@staticmethod
def generate_music(
prompt: str,
tags: str,
instrumental: bool,
lyrics: str,
title: str,
api_key: str = ""
) -> Tuple[Optional[str], str]:
"""Handle text-to-music generation."""
if not prompt.strip():
return None, "Please enter a description for your music."
if not api_key.strip():
return None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = MusicService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
clips = loop.run_until_complete(
service.generate_music(
prompt=prompt,
title=title if title.strip() else None,
tags=tags if tags.strip() else None,
lyrics=lyrics if lyrics.strip() and not instrumental else None,
instrumental=instrumental
)
)
if clips:
audio_path = loop.run_until_complete(
service.download_clip(clips[0])
)
return audio_path, "Music generation complete!"
else:
return None, "No music was generated. Please try a different prompt."
finally:
loop.close()
except Exception as e:
return None, format_error(e)
finally:
service.cleanup()
@staticmethod
def create_cover(
audio_file: str,
style_prompt: str,
tags: str,
title: str,
api_key: str = ""
) -> Tuple[Optional[str], str]:
"""Handle cover song creation."""
if not audio_file:
return None, "Please upload an audio file."
if not style_prompt.strip():
return None, "Please describe the style for your cover."
if not api_key.strip():
return None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = MusicService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
audio_url = f"file://{audio_file}"
clips = loop.run_until_complete(
service.create_cover(
audio_url=audio_url,
style_prompt=style_prompt,
title=title if title.strip() else None,
tags=tags if tags.strip() else None
)
)
if clips:
audio_path = loop.run_until_complete(
service.download_clip(clips[0])
)
return audio_path, "Cover created successfully!"
else:
return None, "No cover was generated. Please try again."
finally:
loop.close()
except Exception as e:
return None, format_error(e)
finally:
service.cleanup()
@staticmethod
def extract_stems(
audio_file: str,
api_key: str = ""
) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], str]:
"""Handle stem extraction."""
if not audio_file:
return None, None, None, None, "Please upload an audio file."
if not api_key.strip():
return None, None, None, None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = MusicService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
audio_url = f"file://{audio_file}"
stems = loop.run_until_complete(
service.extract_stems(audio_url=audio_url)
)
return (
stems.vocals_path,
stems.drums_path,
stems.bass_path,
stems.other_path,
"Stems extracted successfully!"
)
finally:
loop.close()
except Exception as e:
return None, None, None, None, format_error(e)
finally:
service.cleanup()
@staticmethod
def generate_image(
prompt: str,
format_type: str,
api_key: str = ""
) -> Tuple[Optional[str], Optional[str], str]:
"""Handle text-to-image generation.
Returns:
Tuple of (image_url, model_url, status)
- For image/multi: (url, None, status)
- For 3d: (None, url, status)
"""
if not prompt.strip():
return None, None, "Please enter a description for your image."
if not api_key.strip():
return None, None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = ImageService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
results = loop.run_until_complete(
service.generate_image(
prompt=prompt,
format_type=format_type
)
)
if results:
url = results[0].image_url
if format_type == "3d":
# Return as 3D model
return None, url, "3D model generated successfully!"
else:
# Return as image
return url, None, "Image generated successfully!"
else:
return None, None, "No result was generated. Please try a different prompt."
finally:
loop.close()
except Exception as e:
return None, None, format_error(e)
finally:
service.cleanup()
@staticmethod
def edit_image(
input_image: str,
edit_prompt: str,
strength: float,
api_key: str = ""
) -> Tuple[Optional[str], str]:
"""Handle image-to-image editing."""
if not input_image:
return None, "Please upload an image."
if not edit_prompt.strip():
return None, "Please describe how you want to edit the image."
if not api_key.strip():
return None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = ImageService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
image_url = f"file://{input_image}"
images = loop.run_until_complete(
service.edit_image(
image_url=image_url,
edit_prompt=edit_prompt,
strength=strength
)
)
if images:
# Return URL directly - Gradio can display remote images
return images[0].image_url, "Image edited successfully!"
else:
return None, "No edited image was generated. Please try again."
finally:
loop.close()
except Exception as e:
return None, format_error(e)
finally:
service.cleanup()
@staticmethod
def generate_video(
prompt: str,
duration: int,
style: str,
api_key: str = ""
) -> Tuple[Optional[str], str]:
"""Handle text-to-video generation."""
if not prompt.strip():
return None, "Please enter a description for your video."
if not api_key.strip():
return None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = VideoService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
videos = loop.run_until_complete(
service.generate_video(
prompt=prompt,
duration=int(duration),
style=style
)
)
if videos:
# Return URL directly - Gradio can display remote videos
return videos[0].video_url, "Video generated successfully!"
else:
return None, "No video was generated. Please try a different prompt."
finally:
loop.close()
except Exception as e:
return None, format_error(e)
finally:
service.cleanup()
@staticmethod
def animate_image(
input_image: str,
motion_prompt: str,
duration: int,
api_key: str = ""
) -> Tuple[Optional[str], str]:
"""Handle image-to-video animation."""
if not input_image:
return None, "Please upload an image."
if not motion_prompt.strip():
return None, "Please describe the motion you want."
if not api_key.strip():
return None, "Please enter your API key in the Settings section."
client = StackNetClient(api_key=api_key.strip())
service = VideoService(client=client)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
image_url = f"file://{input_image}"
videos = loop.run_until_complete(
service.animate_image(
image_url=image_url,
motion_prompt=motion_prompt,
duration=int(duration)
)
)
if videos:
# Return URL directly - Gradio can display remote videos
return videos[0].video_url, "Image animated successfully!"
else:
return None, "No video was generated. Please try again."
finally:
loop.close()
except Exception as e:
return None, format_error(e)
finally:
service.cleanup()