from __future__ import annotations import json from pathlib import Path from typing import Any import gradio as gr ROOT = Path(__file__).resolve().parent FRONTEND = ROOT / "frontend" def _read_frontend(name: str) -> str: return (FRONTEND / name).read_text(encoding="utf-8") def initial_studio_value() -> dict[str, Any]: return { "state": "idle", "status": "Upload a recording to begin.", "progress": 0, "audio_name": "", "elapsed": 0, "duration": 0, "available_until": 0, "original_audio": "", "note_count": 0, "tracks": [], "full_midi": "", "score_svg": "", "score_pdf_url": "", "score_parts": [], "score_page_urls": [], "score_pages": 0, "notation": {}, } from gradio.events import Dependency class StudioViewer(gr.HTML): """MuScriptor result viewer implemented with Gradio's custom HTML API.""" def __init__(self, value: Any | None = None, **kwargs: Any) -> None: synth_bundle = _read_frontend("spessasynth.bundle.js") viewer_script = _read_frontend("studio.js") super().__init__( value=value or initial_studio_value(), html_template=_read_frontend("studio.html"), css_template=_read_frontend("studio.css"), js_on_load=f"{synth_bundle}\n{viewer_script}", apply_default_css=False, container=False, **kwargs, ) def api_info(self) -> dict[str, Any]: return {"type": "object"} from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING from gradio.blocks import Block if TYPE_CHECKING: from gradio.components import Timer from gradio.components.base import Component def normalize_viewer_value(value: Any) -> dict[str, Any]: if isinstance(value, dict): return value if isinstance(value, str): try: parsed = json.loads(value) if isinstance(parsed, dict): return parsed except json.JSONDecodeError: pass return initial_studio_value()