| """ |
| 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) |
|
|
| |
| 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 None, url, "3D model generated successfully!" |
| else: |
| |
| 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 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 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 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() |
|
|