| | """ |
| | 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, |
| | style: str, |
| | aspect_ratio: str, |
| | api_key: str = "" |
| | ) -> Tuple[Optional[str], str]: |
| | """Handle text-to-image generation.""" |
| | if not prompt.strip(): |
| | return None, "Please enter a description for your 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: |
| | images = loop.run_until_complete( |
| | service.generate_image( |
| | prompt=prompt, |
| | style=style, |
| | aspect_ratio=aspect_ratio |
| | ) |
| | ) |
| |
|
| | if images: |
| | image_path = loop.run_until_complete( |
| | service.download_image(images[0]) |
| | ) |
| | return image_path, "Image generated successfully!" |
| | else: |
| | return None, "No image was generated. Please try a different prompt." |
| |
|
| | finally: |
| | loop.close() |
| |
|
| | except Exception as e: |
| | return 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: |
| | image_path = loop.run_until_complete( |
| | service.download_image(images[0]) |
| | ) |
| | return image_path, "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: |
| | video_path = loop.run_until_complete( |
| | service.download_video(videos[0]) |
| | ) |
| | return video_path, "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: |
| | video_path = loop.run_until_complete( |
| | service.download_video(videos[0]) |
| | ) |
| | return video_path, "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() |
| |
|