Spaces:
Running
Running
| # file: battlewords/sounds.py | |
| import os | |
| import tempfile | |
| import base64 | |
| import requests | |
| import time | |
| from io import BytesIO | |
| from pathlib import Path | |
| # Load environment variables from .env file | |
| def _load_env(): | |
| """Load .env file from project root""" | |
| env_path = Path(__file__).parent.parent / ".env" | |
| if env_path.exists(): | |
| with open(env_path) as f: | |
| for line in f: | |
| line = line.strip() | |
| if line and not line.startswith("#") and "=" in line: | |
| key, value = line.split("=", 1) | |
| os.environ.setdefault(key.strip(), value.strip()) | |
| _load_env() | |
| # Predefined prompts for sound effects: key: {"prompt": text, "duration": seconds} | |
| EFFECT_PROMPTS = { | |
| "correct_guess": {"prompt": "A short, sharp ding sound for a correct guess", "duration": 2}, | |
| "incorrect_guess": {"prompt": "A low buzz sound for an incorrect guess", "duration": 2}, | |
| "miss": {"prompt": "A soft thud sound for a miss", "duration": 1}, | |
| "hit": {"prompt": "A bright chime sound for a hit", "duration": 1}, | |
| "congratulations": {"prompt": "A triumphant fanfare sound for congratulations", "duration": 3} | |
| } | |
| _sound_cache = {} | |
| # Hugging Face Inference API configuration | |
| # Loaded from .env file or environment variable: HF_API_TOKEN | |
| HF_API_TOKEN = os.environ.get("HF_API_TOKEN", None) | |
| if HF_API_TOKEN: | |
| print(f"Using HF_API_TOKEN: {HF_API_TOKEN[:10]}...") | |
| HF_API_URL = "https://api-inference.huggingface.co/models/facebook/audiogen-medium" | |
| def generate_sound_effect(effect: str, save_to_assets: bool = False, use_api: str = "huggingface") -> str: | |
| """ | |
| Generate a sound effect using external API based on the effect string. | |
| Returns the path to the generated audio file. | |
| Args: | |
| effect: Name of the effect (must be in EFFECT_PROMPTS) | |
| save_to_assets: If True, save to battlewords/assets/audio/effects/ instead of temp directory | |
| use_api: API to use - "huggingface" (default) or "replicate" | |
| """ | |
| if effect not in EFFECT_PROMPTS: | |
| raise ValueError(f"Unknown effect: {effect}. Available effects: {list(EFFECT_PROMPTS.keys())}") | |
| # Check cache first (only for temp files) | |
| if effect in _sound_cache and not save_to_assets: | |
| if os.path.exists(_sound_cache[effect]): | |
| return _sound_cache[effect] | |
| effect_config = EFFECT_PROMPTS[effect] | |
| prompt = effect_config["prompt"] | |
| duration = effect_config["duration"] | |
| print(f"Generating sound effect: {effect}") | |
| print(f" Prompt: {prompt}") | |
| print(f" Duration: {duration}s") | |
| print(f" Using API: {use_api}") | |
| audio_bytes = None | |
| if use_api == "huggingface": | |
| audio_bytes = _generate_via_huggingface(prompt, duration) | |
| else: | |
| raise ValueError(f"Unknown API: {use_api}") | |
| if audio_bytes is None: | |
| raise RuntimeError(f"Failed to generate sound effect: {effect}") | |
| # Determine save location | |
| if save_to_assets: | |
| # Save to effects assets directory (preferred by audio.py) | |
| assets_dir = os.path.join(os.path.dirname(__file__), "assets", "audio", "effects") | |
| os.makedirs(assets_dir, exist_ok=True) | |
| filename = f"{effect}.wav" | |
| path = os.path.join(assets_dir, filename) | |
| with open(path, "wb") as f: | |
| f.write(audio_bytes) | |
| print(f" Saved to: {path}") | |
| else: | |
| # Save to temporary file | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile: | |
| tmpfile.write(audio_bytes) | |
| path = tmpfile.name | |
| print(f" Saved to: {path}") | |
| # Cache the path | |
| _sound_cache[effect] = path | |
| return path | |
| def _generate_via_huggingface(prompt: str, duration: float, max_retries: int = 3) -> bytes: | |
| """ | |
| Generate audio using Hugging Face Inference API. | |
| Uses facebook/audiogen-medium model for sound effects. | |
| """ | |
| headers = {} | |
| if HF_API_TOKEN: | |
| headers["Authorization"] = f"Bearer {HF_API_TOKEN}" | |
| payload = { | |
| "inputs": prompt, | |
| "parameters": { | |
| "duration": duration | |
| } | |
| } | |
| for attempt in range(max_retries): | |
| try: | |
| print(f" Calling Hugging Face API (attempt {attempt + 1}/{max_retries})...") | |
| response = requests.post(HF_API_URL, headers=headers, json=payload, timeout=60) | |
| if response.status_code == 503: | |
| # Model is loading, wait and retry | |
| print(f" Model loading, waiting 10 seconds...") | |
| time.sleep(10) | |
| continue | |
| if response.status_code == 200: | |
| print(f" Success! Received {len(response.content)} bytes") | |
| return response.content | |
| else: | |
| print(f" Error {response.status_code}: {response.text}") | |
| if attempt < max_retries - 1: | |
| time.sleep(5) | |
| continue | |
| else: | |
| raise RuntimeError(f"API request failed: {response.status_code} - {response.text}") | |
| except requests.exceptions.Timeout: | |
| print(f" Request timed out") | |
| if attempt < max_retries - 1: | |
| time.sleep(5) | |
| continue | |
| else: | |
| raise RuntimeError("API request timed out after multiple attempts") | |
| except Exception as e: | |
| print(f" Error: {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(5) | |
| continue | |
| else: | |
| raise | |
| return None | |
| def get_sound_effect_path(effect: str) -> str: | |
| """ | |
| Get the path to a sound effect, generating it if necessary. | |
| """ | |
| return generate_sound_effect(effect) | |
| def get_sound_effect_data_url(effect: str) -> str: | |
| """ | |
| Get a data URL for the sound effect, suitable for embedding in HTML. | |
| """ | |
| path = generate_sound_effect(effect) | |
| with open(path, "rb") as f: | |
| data = f.read() | |
| encoded = base64.b64encode(data).decode() | |
| return f"data:audio/wav;base64,{encoded}" | |
| def generate_all_effects(save_to_assets: bool = True): | |
| """ | |
| Generate all sound effects defined in EFFECT_PROMPTS. | |
| Args: | |
| save_to_assets: If True, save to battlewords/assets/audio/effects/ directory | |
| """ | |
| print(f"\nGenerating {len(EFFECT_PROMPTS)} sound effects...") | |
| print("=" * 60) | |
| for effect_name in EFFECT_PROMPTS.keys(): | |
| try: | |
| generate_sound_effect(effect_name, save_to_assets=save_to_assets) | |
| print() | |
| except Exception as e: | |
| print(f"ERROR generating {effect_name}: {e}") | |
| print() | |
| print("=" * 60) | |
| print("Sound effect generation complete!") | |
| if __name__ == "__main__": | |
| # Generate all sound effects when run as a script | |
| generate_all_effects(save_to_assets=True) |