multimodalart HF Staff commited on
Commit
0fe4bbd
·
verified ·
1 Parent(s): 38a3ba0

Fix MIDI player rendering via gr.HTML head/html_template/js_on_load custom component API

Browse files
Files changed (1) hide show
  1. app.py +52 -19
app.py CHANGED
@@ -94,35 +94,62 @@ midi-player { width: 100%; display: block; margin-bottom: 8px; }
94
  midi-visualizer { width: 100%; display: block; overflow: auto; }
95
  """
96
 
97
- # Loads the html-midi-player web components (<midi-player> / <midi-visualizer>)
98
- # from Magenta.js + Tone.js via jsDelivr. Injected once alongside the player.
99
- _MIDI_PLAYER_SCRIPT = (
 
 
 
 
100
  '<script src="https://cdn.jsdelivr.net/combine/'
101
  "npm/tone@14.7.58,"
102
  "npm/@magenta/music@1.23.1/es6/core.js,"
103
  'npm/html-midi-player@1.5.0"></script>'
104
  )
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- def _midi_player_html(midi_bytes: bytes | None) -> str:
108
- """Build an HTML snippet that visualizes and plays back a MIDI file.
 
109
 
110
  The MIDI data is embedded directly as a base64 data URI so it needs no
111
- separate file-serving route. Uses the html-midi-player web components
112
- (Magenta.js <midi-player> / <midi-visualizer>).
113
  """
114
  if not midi_bytes:
115
  return ""
116
  b64 = base64.b64encode(midi_bytes).decode("ascii")
117
- src = f"data:audio/midi;base64,{b64}"
118
- return (
119
- f"{_MIDI_PLAYER_SCRIPT}"
120
- '<div class="midi-player-container">'
121
- f'<midi-player src="{src}" sound-font '
122
- 'visualizer=".midi-visualizer"></midi-player>'
123
- '<midi-visualizer class="midi-visualizer" type="piano-roll"></midi-visualizer>'
124
- "</div>"
125
- )
126
 
127
 
128
  @spaces.GPU(duration=120)
@@ -190,9 +217,9 @@ def transcribe_audio(
190
  f"Play it back below or download the MIDI file."
191
  )
192
 
193
- player_html = _midi_player_html(midi_bytes)
194
 
195
- return tmp.name, summary, player_html
196
 
197
 
198
  with gr.Blocks() as demo:
@@ -239,7 +266,13 @@ with gr.Blocks() as demo:
239
 
240
  transcribe_btn = gr.Button("Transcribe", variant="primary")
241
 
242
- midi_player = gr.HTML(label="MIDI playback & visualization")
 
 
 
 
 
 
243
  midi_output = gr.File(label="Download MIDI file")
244
  summary_output = gr.Textbox(label="Summary", interactive=False)
245
 
 
94
  midi-visualizer { width: 100%; display: block; overflow: auto; }
95
  """
96
 
97
+ # The html-midi-player web components (<midi-player> / <midi-visualizer>) rely
98
+ # on Magenta.js + Tone.js. Following Gradio's official custom-HTML-components
99
+ # guide (https://gradio.app/guides/custom-HTML-components), the library is
100
+ # loaded via the gr.HTML `head` parameter instead of embedding <script> tags in
101
+ # the component value — Gradio sanitizes value HTML and strips <script> tags,
102
+ # which is why the raw-HTML approach never mounted the web components.
103
+ _MIDI_PLAYER_HEAD = (
104
  '<script src="https://cdn.jsdelivr.net/combine/'
105
  "npm/tone@14.7.58,"
106
  "npm/@magenta/music@1.23.1/es6/core.js,"
107
  'npm/html-midi-player@1.5.0"></script>'
108
  )
109
 
110
+ # Static markup for the player/visualizer. js_on_load wires the transcribed
111
+ # MIDI (a base64 data URI passed as the component value) into the <midi-player>
112
+ # and links it to the <midi-visualizer> so both mount and render.
113
+ _MIDI_PLAYER_TEMPLATE = (
114
+ '<div class="midi-player-container">'
115
+ '<midi-player sound-font visualizer=".midi-visualizer"></midi-player>'
116
+ '<midi-visualizer class="midi-visualizer" type="piano-roll"></midi-visualizer>'
117
+ "</div>"
118
+ )
119
+
120
+ # Runs once when the component loads. `element` is the component's DOM node,
121
+ # `props.value` is the base64 data URI (or "" when there's nothing yet).
122
+ # `watch('value', ...)` re-runs the sync every time the server pushes a new
123
+ # value (i.e. after each transcription), so the web components actually update.
124
+ _MIDI_PLAYER_JS = """
125
+ function syncPlayer() {
126
+ const player = element.querySelector('midi-player');
127
+ const container = element.querySelector('.midi-player-container');
128
+ if (!player || !container) { return; }
129
+ if (props.value) {
130
+ container.style.display = 'block';
131
+ player.src = props.value;
132
+ } else {
133
+ container.style.display = 'none';
134
+ player.removeAttribute('src');
135
+ }
136
+ }
137
+ syncPlayer();
138
+ watch('value', syncPlayer);
139
+ """
140
 
141
+
142
+ def _midi_player_src(midi_bytes: bytes | None) -> str:
143
+ """Return the transcribed MIDI as a base64 data URI for the player.
144
 
145
  The MIDI data is embedded directly as a base64 data URI so it needs no
146
+ separate file-serving route. The <midi-player> / <midi-visualizer> web
147
+ components consume this via the gr.HTML custom component's js_on_load.
148
  """
149
  if not midi_bytes:
150
  return ""
151
  b64 = base64.b64encode(midi_bytes).decode("ascii")
152
+ return f"data:audio/midi;base64,{b64}"
 
 
 
 
 
 
 
 
153
 
154
 
155
  @spaces.GPU(duration=120)
 
217
  f"Play it back below or download the MIDI file."
218
  )
219
 
220
+ player_src = _midi_player_src(midi_bytes)
221
 
222
+ return tmp.name, summary, player_src
223
 
224
 
225
  with gr.Blocks() as demo:
 
266
 
267
  transcribe_btn = gr.Button("Transcribe", variant="primary")
268
 
269
+ midi_player = gr.HTML(
270
+ value="",
271
+ label="MIDI playback & visualization",
272
+ head=_MIDI_PLAYER_HEAD,
273
+ html_template=_MIDI_PLAYER_TEMPLATE,
274
+ js_on_load=_MIDI_PLAYER_JS,
275
+ )
276
  midi_output = gr.File(label="Download MIDI file")
277
  summary_output = gr.Textbox(label="Summary", interactive=False)
278