from __future__ import annotations import json import tempfile import uuid from pathlib import Path import gradio as gr import soundfile as sf try: import spaces except ImportError: class spaces: class GPU: def __init__(self, func=None, duration=60): self.func = func def __call__(self, *args, **kwargs): if self.func is not None: return self.func(*args, **kwargs) return args[0] from pyharp import ModelCard, build_endpoint from music2emo_runtime import analyze_music MIN_AUDIO_SECONDS = 1 MAX_AUDIO_SECONDS = 60 OUTPUT_ROOT = Path(tempfile.gettempdir()) / "music2emo_outputs" model_card = ModelCard( name="Music2Emo", description=( "Recognize music emotion as mood tags and continuous " "valence-arousal scores." ), author="AMAAI Lab", tags=[ "music-information-retrieval", "music-emotion-recognition", "mood-tagging", "valence", "arousal", ], ) def _validate_audio(path: str | None) -> str: if not path: raise gr.Error("Please upload an audio file.") try: duration = sf.info(path).duration except Exception as exc: raise gr.Error(f"Could not read the audio file: {exc}") from exc if duration < MIN_AUDIO_SECONDS: raise gr.Error("Audio must be at least 1 second long.") if duration > MAX_AUDIO_SECONDS: raise gr.Error( f"Audio must be no longer than {MAX_AUDIO_SECONDS} seconds. " f"Received {duration:.1f} seconds." ) return path @spaces.GPU(duration=120) def process_fn(audio_path: str | None, threshold: float) -> str: audio_path = _validate_audio(audio_path) try: result = analyze_music(audio_path, threshold) except Exception as exc: raise gr.Error(f"Music2Emo inference failed: {exc}") from exc output_dir = OUTPUT_ROOT / uuid.uuid4().hex output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / "music2emo_analysis.json" output_path.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") return str(output_path) with gr.Blocks(title="Music2Emo") as demo: input_components = [ gr.Audio(type="filepath", label="Music Audio") .harp_required(True) .set_info("Music clip between 1 and 60 seconds."), gr.Slider( minimum=0.1, maximum=0.9, value=0.5, step=0.05, label="Mood Threshold", ).set_info("Minimum probability for a mood tag to be returned."), ] output_components = [ gr.File( type="filepath", file_types=[".json"], label="Emotion Analysis", ).set_info("Mood probabilities and valence-arousal scores."), ] build_endpoint( model_card=model_card, input_components=input_components, output_components=output_components, process_fn=process_fn, ) if __name__ == "__main__": demo.queue(default_concurrency_limit=1).launch(show_error=True, pwa=True)