clement-pages commited on
Commit
50c57e9
·
1 Parent(s): a9b52e3

downgrade to gradio 4.27.0

Browse files
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 💬
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: gradio
7
- sdk_version: 5.6.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
 
4
  colorFrom: yellow
5
  colorTo: green
6
  sdk: gradio
7
+ sdk_version: 4.27.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
sourceviewer/README.md CHANGED
@@ -1,15 +1,10 @@
1
- ---
2
- tags: [gradio-custom-component, Audio]
3
- title: gradio_sourceviewer
4
- short_description: A gradio custom component
5
- colorFrom: blue
6
- colorTo: yellow
7
- sdk: gradio
8
- pinned: false
9
- app_file: space.py
10
- ---
11
 
12
  # gradio_sourceviewer
 
13
 
14
- You can auto-generate documentation for your custom component with the `gradio cc docs` command.
15
- You can also edit this file however you like.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
  # gradio_sourceviewer
3
+ A Custom Gradio component.
4
 
5
+ ## Example usage
6
+
7
+ ```python
8
+ import gradio as gr
9
+ from gradio_sourceviewer import SourceViewer
10
+ ```
sourceviewer/backend/gradio_sourceviewer/sourceviewer.py CHANGED
@@ -3,28 +3,21 @@
3
  from __future__ import annotations
4
 
5
  import dataclasses
6
- import io
7
- from collections.abc import Callable, Sequence
8
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Literal
10
 
11
- import anyio
12
  import httpx
13
  import numpy as np
14
- from gradio_client import handle_file
15
  from gradio_client import utils as client_utils
16
  from gradio_client.documentation import document
17
- from pydub import AudioSegment
18
 
19
- from gradio import processing_utils, utils, wasm_utils
20
  from gradio.components.base import Component, StreamingInput, StreamingOutput
21
- from gradio.data_classes import FileData, FileDataDict, MediaStreamChunk
22
  from gradio.events import Events
23
  from gradio.exceptions import Error
24
 
25
- if TYPE_CHECKING:
26
- from gradio.components import Timer
27
-
28
 
29
  @dataclasses.dataclass
30
  class WaveformOptions:
@@ -72,7 +65,6 @@ class SourceViewer(
72
  Events.pause_recording,
73
  Events.stop_recording,
74
  Events.upload,
75
- Events.input,
76
  ]
77
 
78
  data_model = FileData
@@ -81,13 +73,10 @@ class SourceViewer(
81
  self,
82
  value: str | Path | tuple[int, np.ndarray] | Callable | None = None,
83
  *,
84
- sources: list[Literal["upload", "microphone"]]
85
- | Literal["upload", "microphone"]
86
- | None = None,
87
  type: Literal["numpy", "filepath"] = "numpy",
88
  label: str | None = None,
89
- every: Timer | float | None = None,
90
- inputs: Component | Sequence[Component] | set[Component] | None = None,
91
  show_label: bool | None = None,
92
  container: bool = True,
93
  scale: int | None = None,
@@ -98,8 +87,7 @@ class SourceViewer(
98
  elem_id: str | None = None,
99
  elem_classes: list[str] | str | None = None,
100
  render: bool = True,
101
- key: int | str | None = None,
102
- format: Literal["wav", "mp3"] | None = None,
103
  autoplay: bool = False,
104
  show_download_button: bool | None = None,
105
  show_share_button: bool | None = None,
@@ -107,17 +95,14 @@ class SourceViewer(
107
  min_length: int | None = None,
108
  max_length: int | None = None,
109
  waveform_options: WaveformOptions | dict | None = None,
110
- loop: bool = False,
111
- recording: bool = False,
112
  ):
113
  """
114
  Parameters:
115
  value: A path, URL, or [sample_rate, numpy array] tuple (sample rate in Hz, audio data as a float or int numpy array) for the default value that SourceViewer component is going to take. If callable, the function will be called whenever the app loads to set the initial value of the component.
116
  sources: A list of sources permitted for audio. "upload" creates a box where user can drop an audio file, "microphone" creates a microphone input. The first element in the list will be used as the default source. If None, defaults to ["upload", "microphone"], or ["microphone"] if `streaming` is True.
117
  type: The format the audio file is converted to before being passed into the prediction function. "numpy" converts the audio to a tuple consisting of: (int sample rate, numpy.array for the data), "filepath" passes a str path to a temporary file containing the audio.
118
- label: the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
119
- every: Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
120
- inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
121
  show_label: if True, will display label.
122
  container: If True, will place the component in a container - providing some extra padding around the border.
123
  scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
@@ -127,18 +112,15 @@ class SourceViewer(
127
  streaming: If set to True when used in a `live` interface as an input, will automatically stream webcam feed. When used set as an output, takes audio chunks yield from the backend and combines them into one streaming audio output.
128
  elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
129
  elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
130
- render: if False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
131
- key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
132
- format: the file extension with which to save audio files. Either 'wav' or 'mp3'. wav files are lossless but will tend to be larger files. mp3 files tend to be smaller. This parameter applies both when this component is used as an input (and `type` is "filepath") to determine which file format to convert user-provided audio to, and when this component is used as an output to determine the format of audio returned to the user. If None, no file format conversion is done and the audio is kept as is. In the case where output audio is returned from the prediction function as numpy array and no `format` is provided, it will be returned as a "wav" file.
133
  autoplay: Whether to automatically play the audio when the component is used as an output. Note: browsers will not autoplay audio files if the user has not interacted with the page yet.
134
  show_download_button: If True, will show a download button in the corner of the component for saving audio. If False, icon does not appear. By default, it will be True for output components and False for input components.
135
  show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
136
  editable: If True, allows users to manipulate the audio file if the component is interactive. Defaults to True.
137
  min_length: The minimum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no minimum length.
138
  max_length: The maximum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no maximum length.
139
- waveform_options: A dictionary of options for the waveform display. Options include: waveform_color (str), waveform_progress_color (str), show_controls (bool), skip_length (int), trim_region_color (str). Default is None, which uses the default values for these options. [See `gr.WaveformOptions` docs](#waveform-options).
140
- loop: If True, the audio will loop when it reaches the end and continue playing from the beginning.
141
- recording: If True, the audio component will be set to record audio from the microphone if the source is set to "microphone". Defaults to False.
142
  """
143
  valid_sources: list[Literal["upload", "microphone"]] = ["upload", "microphone"]
144
  if sources is None:
@@ -159,7 +141,7 @@ class SourceViewer(
159
  valid_types = ["numpy", "filepath"]
160
  if type not in valid_types:
161
  raise ValueError(
162
- f"Invalid value for parameter `type`: {type}. Please choose from one of: {' '.join(valid_types)}"
163
  )
164
  self.type = type
165
  self.streaming = streaming
@@ -167,14 +149,8 @@ class SourceViewer(
167
  raise ValueError(
168
  "SourceViewer streaming only available if sources includes 'microphone'."
169
  )
170
- valid_formats = ["wav", "mp3"]
171
- if format is not None and format.lower() not in valid_formats:
172
- raise ValueError(
173
- f"Invalid value for parameter `format`: {format}. Please choose from one of: {' '.join(valid_formats)}"
174
- )
175
- self.format = format and format.lower()
176
  self.autoplay = autoplay
177
- self.loop = loop
178
  self.show_download_button = show_download_button
179
  self.show_share_button = (
180
  (utils.get_space() is not None)
@@ -190,11 +166,9 @@ class SourceViewer(
190
  self.waveform_options = waveform_options
191
  self.min_length = min_length
192
  self.max_length = max_length
193
- self.recording = recording
194
  super().__init__(
195
  label=label,
196
  every=every,
197
- inputs=inputs,
198
  show_label=show_label,
199
  container=container,
200
  scale=scale,
@@ -204,12 +178,11 @@ class SourceViewer(
204
  elem_id=elem_id,
205
  elem_classes=elem_classes,
206
  render=render,
207
- key=key,
208
  value=value,
209
  )
210
 
211
  def example_payload(self) -> Any:
212
- return handle_file(
213
  "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav"
214
  )
215
 
@@ -231,31 +204,29 @@ class SourceViewer(
231
  if not payload.path:
232
  raise ValueError("payload path missing")
233
 
234
- needs_conversion = False
235
- original_suffix = Path(payload.path).suffix.lower()
236
- if self.format is not None and original_suffix != f".{self.format}":
237
- needs_conversion = True
 
 
238
 
239
- if self.min_length is not None or self.max_length is not None:
240
- sample_rate, data = processing_utils.audio_from_file(payload.path)
241
- duration = len(data) / sample_rate
242
- if self.min_length is not None and duration < self.min_length:
243
- raise Error(
244
- f"SourceViewer is too short, and must be at least {self.min_length} seconds"
245
- )
246
- if self.max_length is not None and duration > self.max_length:
247
- raise Error(
248
- f"SourceViewer is too long, and must be at most {self.max_length} seconds"
249
- )
250
 
251
  if self.type == "numpy":
252
- return processing_utils.audio_from_file(payload.path)
253
  elif self.type == "filepath":
254
- if not needs_conversion:
255
- return payload.path
256
- sample_rate, data = processing_utils.audio_from_file(payload.path)
257
- output_file = str(Path(payload.path).with_suffix(f".{self.format}"))
258
- assert self.format is not None # noqa: S101
259
  processing_utils.audio_to_file(
260
  sample_rate, data, output_file, format=self.format
261
  )
@@ -279,7 +250,6 @@ class SourceViewer(
279
  orig_name = None
280
  if value is None:
281
  return None
282
-
283
  if isinstance(value, bytes):
284
  if self.streaming:
285
  return value
@@ -290,95 +260,48 @@ class SourceViewer(
290
  elif isinstance(value, tuple):
291
  sample_rate, data = value
292
  file_path = processing_utils.save_audio_to_cache(
293
- data,
294
- sample_rate,
295
- format=self.format or "wav",
296
- cache_dir=self.GRADIO_CACHE,
297
  )
298
  orig_name = Path(file_path).name
299
- elif isinstance(value, (str, Path)):
300
- original_suffix = Path(value).suffix.lower()
301
- if self.format is not None and original_suffix != f".{self.format}":
302
- sample_rate, data = processing_utils.audio_from_file(str(value))
303
- file_path = processing_utils.save_audio_to_cache(
304
- data, sample_rate, format=self.format, cache_dir=self.GRADIO_CACHE
305
- )
306
- else:
307
- file_path = str(value)
308
- orig_name = Path(file_path).name if Path(file_path).exists() else None
309
  else:
310
- raise ValueError(f"Cannot process {value} as SourceViewer")
 
 
 
311
  return FileData(path=file_path, orig_name=orig_name)
312
 
313
- @staticmethod
314
- def _convert_to_adts(data: bytes):
315
- if wasm_utils.IS_WASM:
316
- raise wasm_utils.WasmUnsupportedError(
317
- "SourceViewer streaming is not supported in the Wasm mode."
318
- )
319
- segment = AudioSegment.from_file(io.BytesIO(data))
320
-
321
- buffer = io.BytesIO()
322
- segment.export(buffer, format="adts") # ADTS is a container format for AAC
323
- aac_data = buffer.getvalue()
324
- return aac_data, len(segment) / 1000.0
325
-
326
- @staticmethod
327
- async def covert_to_adts(data: bytes) -> tuple[bytes, float]:
328
- return await anyio.to_thread.run_sync(SourceViewer._convert_to_adts, data)
329
-
330
- async def stream_output(
331
- self,
332
- value,
333
- output_id: str,
334
- first_chunk: bool, # noqa: ARG002
335
- ) -> tuple[MediaStreamChunk | None, FileDataDict]:
336
- output_file: FileDataDict = {
337
  "path": output_id,
338
  "is_stream": True,
339
- "orig_name": "audio-stream.mp3",
340
- "meta": {"_type": "gradio.FileData"},
341
  }
342
  if value is None:
343
  return None, output_file
344
  if isinstance(value, bytes):
345
- value, duration = await self.covert_to_adts(value)
346
- return {
347
- "data": value,
348
- "duration": duration,
349
- "extension": ".aac",
350
- }, output_file
351
  if client_utils.is_http_url_like(value["path"]):
352
  response = httpx.get(value["path"])
353
  binary_data = response.content
354
  else:
355
  output_file["orig_name"] = value["orig_name"]
356
  file_path = value["path"]
 
357
  with open(file_path, "rb") as f:
358
  binary_data = f.read()
359
- value, duration = await self.covert_to_adts(binary_data)
360
- return {"data": value, "duration": duration, "extension": ".aac"}, output_file
361
-
362
- async def combine_stream(
363
- self,
364
- stream: list[bytes],
365
- desired_output_format: str | None = None,
366
- only_file=False, # noqa: ARG002
367
- ) -> FileData:
368
- output_file = FileData(
369
- path=processing_utils.save_bytes_to_cache(
370
- b"".join(stream), "audio.mp3", cache_dir=self.GRADIO_CACHE
371
- ),
372
- is_stream=False,
373
- orig_name="audio-stream.mp3",
374
- )
375
- if desired_output_format and desired_output_format != "mp3":
376
- new_path = Path(output_file.path).with_suffix(f".{desired_output_format}")
377
- AudioSegment.from_file(output_file.path).export(
378
- new_path, format=desired_output_format
379
- )
380
- output_file.path = str(new_path)
381
- return output_file
382
 
383
  def process_example(
384
  self, value: tuple[int, np.ndarray] | str | Path | bytes | None
 
3
  from __future__ import annotations
4
 
5
  import dataclasses
 
 
6
  from pathlib import Path
7
+ from typing import Any, Callable, Literal
8
 
 
9
  import httpx
10
  import numpy as np
11
+ from gradio_client import file
12
  from gradio_client import utils as client_utils
13
  from gradio_client.documentation import document
 
14
 
15
+ from gradio import processing_utils, utils
16
  from gradio.components.base import Component, StreamingInput, StreamingOutput
17
+ from gradio.data_classes import FileData
18
  from gradio.events import Events
19
  from gradio.exceptions import Error
20
 
 
 
 
21
 
22
  @dataclasses.dataclass
23
  class WaveformOptions:
 
65
  Events.pause_recording,
66
  Events.stop_recording,
67
  Events.upload,
 
68
  ]
69
 
70
  data_model = FileData
 
73
  self,
74
  value: str | Path | tuple[int, np.ndarray] | Callable | None = None,
75
  *,
76
+ sources: list[Literal["upload", "microphone"]] | None = None,
 
 
77
  type: Literal["numpy", "filepath"] = "numpy",
78
  label: str | None = None,
79
+ every: float | None = None,
 
80
  show_label: bool | None = None,
81
  container: bool = True,
82
  scale: int | None = None,
 
87
  elem_id: str | None = None,
88
  elem_classes: list[str] | str | None = None,
89
  render: bool = True,
90
+ format: Literal["wav", "mp3"] = "wav",
 
91
  autoplay: bool = False,
92
  show_download_button: bool | None = None,
93
  show_share_button: bool | None = None,
 
95
  min_length: int | None = None,
96
  max_length: int | None = None,
97
  waveform_options: WaveformOptions | dict | None = None,
 
 
98
  ):
99
  """
100
  Parameters:
101
  value: A path, URL, or [sample_rate, numpy array] tuple (sample rate in Hz, audio data as a float or int numpy array) for the default value that SourceViewer component is going to take. If callable, the function will be called whenever the app loads to set the initial value of the component.
102
  sources: A list of sources permitted for audio. "upload" creates a box where user can drop an audio file, "microphone" creates a microphone input. The first element in the list will be used as the default source. If None, defaults to ["upload", "microphone"], or ["microphone"] if `streaming` is True.
103
  type: The format the audio file is converted to before being passed into the prediction function. "numpy" converts the audio to a tuple consisting of: (int sample rate, numpy.array for the data), "filepath" passes a str path to a temporary file containing the audio.
104
+ label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
105
+ every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
 
106
  show_label: if True, will display label.
107
  container: If True, will place the component in a container - providing some extra padding around the border.
108
  scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
 
112
  streaming: If set to True when used in a `live` interface as an input, will automatically stream webcam feed. When used set as an output, takes audio chunks yield from the backend and combines them into one streaming audio output.
113
  elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
114
  elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
115
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
116
+ format: The file format to save audio files. Either 'wav' or 'mp3'. wav files are lossless but will tend to be larger files. mp3 files tend to be smaller. Default is wav. Applies both when this component is used as an input (when `type` is "format") and when this component is used as an output.
 
117
  autoplay: Whether to automatically play the audio when the component is used as an output. Note: browsers will not autoplay audio files if the user has not interacted with the page yet.
118
  show_download_button: If True, will show a download button in the corner of the component for saving audio. If False, icon does not appear. By default, it will be True for output components and False for input components.
119
  show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
120
  editable: If True, allows users to manipulate the audio file if the component is interactive. Defaults to True.
121
  min_length: The minimum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no minimum length.
122
  max_length: The maximum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no maximum length.
123
+ waveform_options: A dictionary of options for the waveform display. Options include: waveform_color (str), waveform_progress_color (str), show_controls (bool), skip_length (int), trim_region_color (str). Default is None, which uses the default values for these options.
 
 
124
  """
125
  valid_sources: list[Literal["upload", "microphone"]] = ["upload", "microphone"]
126
  if sources is None:
 
141
  valid_types = ["numpy", "filepath"]
142
  if type not in valid_types:
143
  raise ValueError(
144
+ f"Invalid value for parameter `type`: {type}. Please choose from one of: {valid_types}"
145
  )
146
  self.type = type
147
  self.streaming = streaming
 
149
  raise ValueError(
150
  "SourceViewer streaming only available if sources includes 'microphone'."
151
  )
152
+ self.format = format
 
 
 
 
 
153
  self.autoplay = autoplay
 
154
  self.show_download_button = show_download_button
155
  self.show_share_button = (
156
  (utils.get_space() is not None)
 
166
  self.waveform_options = waveform_options
167
  self.min_length = min_length
168
  self.max_length = max_length
 
169
  super().__init__(
170
  label=label,
171
  every=every,
 
172
  show_label=show_label,
173
  container=container,
174
  scale=scale,
 
178
  elem_id=elem_id,
179
  elem_classes=elem_classes,
180
  render=render,
 
181
  value=value,
182
  )
183
 
184
  def example_payload(self) -> Any:
185
+ return file(
186
  "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav"
187
  )
188
 
 
204
  if not payload.path:
205
  raise ValueError("payload path missing")
206
 
207
+ # Need a unique name for the file to avoid re-using the same audio file if
208
+ # a user submits the same audio file twice
209
+ temp_file_path = Path(payload.path)
210
+ output_file_name = str(
211
+ temp_file_path.with_name(f"{temp_file_path.stem}{temp_file_path.suffix}")
212
+ )
213
 
214
+ sample_rate, data = processing_utils.audio_from_file(temp_file_path)
215
+
216
+ duration = len(data) / sample_rate
217
+ if self.min_length is not None and duration < self.min_length:
218
+ raise Error(
219
+ f"SourceViewer is too short, and must be at least {self.min_length} seconds"
220
+ )
221
+ if self.max_length is not None and duration > self.max_length:
222
+ raise Error(
223
+ f"SourceViewer is too long, and must be at most {self.max_length} seconds"
224
+ )
225
 
226
  if self.type == "numpy":
227
+ return sample_rate, data
228
  elif self.type == "filepath":
229
+ output_file = str(Path(output_file_name).with_suffix(f".{self.format}"))
 
 
 
 
230
  processing_utils.audio_to_file(
231
  sample_rate, data, output_file, format=self.format
232
  )
 
250
  orig_name = None
251
  if value is None:
252
  return None
 
253
  if isinstance(value, bytes):
254
  if self.streaming:
255
  return value
 
260
  elif isinstance(value, tuple):
261
  sample_rate, data = value
262
  file_path = processing_utils.save_audio_to_cache(
263
+ data, sample_rate, format=self.format, cache_dir=self.GRADIO_CACHE
 
 
 
264
  )
265
  orig_name = Path(file_path).name
 
 
 
 
 
 
 
 
 
 
266
  else:
267
+ if not isinstance(value, (str, Path)):
268
+ raise ValueError(f"Cannot process {value} as SourceViewer")
269
+ file_path = str(value)
270
+ orig_name = Path(file_path).name if Path(file_path).exists() else None
271
  return FileData(path=file_path, orig_name=orig_name)
272
 
273
+ def stream_output(
274
+ self, value, output_id: str, first_chunk: bool
275
+ ) -> tuple[bytes | None, Any]:
276
+ output_file = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  "path": output_id,
278
  "is_stream": True,
 
 
279
  }
280
  if value is None:
281
  return None, output_file
282
  if isinstance(value, bytes):
283
+ return value, output_file
 
 
 
 
 
284
  if client_utils.is_http_url_like(value["path"]):
285
  response = httpx.get(value["path"])
286
  binary_data = response.content
287
  else:
288
  output_file["orig_name"] = value["orig_name"]
289
  file_path = value["path"]
290
+ is_wav = file_path.endswith(".wav")
291
  with open(file_path, "rb") as f:
292
  binary_data = f.read()
293
+ if is_wav:
294
+ # strip length information from first chunk header, remove headers entirely from subsequent chunks
295
+ if first_chunk:
296
+ binary_data = (
297
+ binary_data[:4] + b"\xFF\xFF\xFF\xFF" + binary_data[8:]
298
+ )
299
+ binary_data = (
300
+ binary_data[:40] + b"\xFF\xFF\xFF\xFF" + binary_data[44:]
301
+ )
302
+ else:
303
+ binary_data = binary_data[44:]
304
+ return binary_data, output_file
 
 
 
 
 
 
 
 
 
 
 
305
 
306
  def process_example(
307
  self, value: tuple[int, np.ndarray] | str | Path | bytes | None
sourceviewer/demo/app.py CHANGED
@@ -1,16 +1,22 @@
1
-
2
  import gradio as gr
3
  from gradio_sourceviewer import SourceViewer
 
 
 
 
 
 
 
 
 
4
 
5
 
6
- example = SourceViewer().example_value()
 
 
 
7
 
8
- demo = gr.Interface(
9
- lambda x:x,
10
- SourceViewer(), # interactive version of your component
11
- SourceViewer(), # static version of your component
12
- # examples=[[example]], # uncomment this line to view the "example version" of your component
13
- )
14
 
15
 
16
  if __name__ == "__main__":
 
 
1
  import gradio as gr
2
  from gradio_sourceviewer import SourceViewer
3
+ from pyannote.audio import Pipeline
4
+ import os
5
+
6
+
7
+ def apply_pipeline(audio: str) -> tuple:
8
+ pipeline = Pipeline.from_pretrained(
9
+ "pyannote/speech-separation-ami-1.0", use_auth_token=os.environ["HF_TOKEN"]
10
+ )
11
+ return pipeline(audio)
12
 
13
 
14
+ with gr.Blocks() as demo:
15
+ audio = gr.Audio(type="filepath")
16
+ btn = gr.Button("Apply separation pipeline")
17
+ source_viewer = SourceViewer()
18
 
19
+ btn.click(fn=apply_pipeline, inputs=[audio], outputs=[source_viewer])
 
 
 
 
 
20
 
21
 
22
  if __name__ == "__main__":
sourceviewer/demo/requirements.txt DELETED
@@ -1 +0,0 @@
1
- gradio_sourceviewer
 
 
sourceviewer/frontend/Index.svelte CHANGED
@@ -5,7 +5,6 @@
5
 
6
  import type { FileData } from "@gradio/client";
7
  import type { LoadingStatus } from "@gradio/statustracker";
8
- import { afterUpdate, onMount } from "svelte";
9
 
10
  import StaticAudio from "./static/StaticAudio.svelte";
11
  import InteractiveAudio from "./interactive/InteractiveAudio.svelte";
@@ -13,7 +12,6 @@
13
  import { Block, UploadText } from "@gradio/atoms";
14
  import type { WaveformOptions } from "./shared/types";
15
 
16
- export let value_is_output = false;
17
  export let elem_id = "";
18
  export let elem_classes: string[] = [];
19
  export let visible = true;
@@ -32,31 +30,13 @@
32
  export let min_width: number | undefined = undefined;
33
  export let loading_status: LoadingStatus;
34
  export let autoplay = false;
35
- export let loop = false;
36
  export let show_download_button: boolean;
37
  export let show_share_button = false;
38
  export let editable = true;
39
  export let waveform_options: WaveformOptions = {};
40
  export let pending: boolean;
41
  export let streaming: boolean;
42
- export let stream_every: number;
43
- export let input_ready: boolean;
44
- export let recording = false;
45
- let uploading = false;
46
- $: input_ready = !uploading;
47
-
48
- let stream_state = "closed";
49
- let _modify_stream: (state: "open" | "closed" | "waiting") => void;
50
- export function modify_stream_state(
51
- state: "open" | "closed" | "waiting"
52
- ): void {
53
- stream_state = state;
54
- _modify_stream(state);
55
- }
56
- export const get_stream_state: () => void = () => stream_state;
57
- export let set_time_limit: (time: number) => void;
58
  export let gradio: Gradio<{
59
- input: never;
60
  change: typeof value;
61
  stream: typeof value;
62
  error: string;
@@ -72,8 +52,6 @@
72
  upload: never;
73
  clear: never;
74
  share: ShareData;
75
- clear_status: LoadingStatus;
76
- close_stream: string;
77
  }>;
78
 
79
  let old_value: null | FileData = null;
@@ -98,9 +76,6 @@
98
  if (JSON.stringify(value) !== JSON.stringify(old_value)) {
99
  old_value = value;
100
  gradio.dispatch("change");
101
- if (!value_is_output) {
102
- gradio.dispatch("input");
103
- }
104
  }
105
  }
106
 
@@ -112,23 +87,14 @@
112
 
113
  let waveform_settings: Record<string, any>;
114
 
115
- let color_accent = "darkorange";
116
-
117
- onMount(() => {
118
- color_accent = getComputedStyle(document?.documentElement).getPropertyValue(
119
- "--color-accent"
120
- );
121
- set_trim_region_colour();
122
- waveform_settings.waveColor = waveform_options.waveform_color || "#9ca3af";
123
- waveform_settings.progressColor =
124
- waveform_options.waveform_progress_color || color_accent;
125
- waveform_settings.mediaControls = waveform_options.show_controls;
126
- waveform_settings.sampleRate = waveform_options.sample_rate || 44100;
127
- });
128
 
129
  $: waveform_settings = {
130
  height: 50,
131
-
 
132
  barWidth: 2,
133
  barGap: 3,
134
  cursorWidth: 2,
@@ -137,7 +103,9 @@
137
  barRadius: 10,
138
  dragToSeek: true,
139
  normalize: true,
140
- minPxPerSec: 20
 
 
141
  };
142
 
143
  const trim_region_settings = {
@@ -153,6 +121,8 @@
153
  );
154
  }
155
 
 
 
156
  function handle_error({ detail }: CustomEvent<string>): void {
157
  const [level, status] = detail.includes("Invalid file type")
158
  ? ["warning", "complete"]
@@ -162,10 +132,6 @@
162
  loading_status.message = detail;
163
  gradio.dispatch(level as "error" | "warning", detail);
164
  }
165
-
166
- afterUpdate(() => {
167
- value_is_output = false;
168
- });
169
  </script>
170
 
171
  {#if !interactive}
@@ -185,7 +151,6 @@
185
  autoscroll={gradio.autoscroll}
186
  i18n={gradio.i18n}
187
  {...loading_status}
188
- on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
189
  />
190
 
191
  <StaticAudio
@@ -195,7 +160,6 @@
195
  {show_share_button}
196
  {value}
197
  {label}
198
- {loop}
199
  {waveform_settings}
200
  {waveform_options}
201
  {editable}
@@ -223,7 +187,6 @@
223
  autoscroll={gradio.autoscroll}
224
  i18n={gradio.i18n}
225
  {...loading_status}
226
- on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
227
  />
228
  <InteractiveAudio
229
  {label}
@@ -241,13 +204,9 @@
241
  {active_source}
242
  {pending}
243
  {streaming}
244
- bind:recording
245
- {loop}
246
- max_file_size={gradio.max_file_size}
247
  {handle_reset_value}
248
  {editable}
249
  bind:dragging
250
- bind:uploading
251
  on:edit={() => gradio.dispatch("edit")}
252
  on:play={() => gradio.dispatch("play")}
253
  on:pause={() => gradio.dispatch("pause")}
@@ -258,16 +217,10 @@
258
  on:upload={() => gradio.dispatch("upload")}
259
  on:clear={() => gradio.dispatch("clear")}
260
  on:error={handle_error}
261
- on:close_stream={() => gradio.dispatch("close_stream", "stream")}
262
  i18n={gradio.i18n}
263
  {waveform_settings}
264
  {waveform_options}
265
  {trim_region_settings}
266
- {stream_every}
267
- bind:modify_stream={_modify_stream}
268
- bind:set_time_limit
269
- upload={(...args) => gradio.client.upload(...args)}
270
- stream_handler={(...args) => gradio.client.stream(...args)}
271
  >
272
  <UploadText i18n={gradio.i18n} type="audio" />
273
  </InteractiveAudio>
 
5
 
6
  import type { FileData } from "@gradio/client";
7
  import type { LoadingStatus } from "@gradio/statustracker";
 
8
 
9
  import StaticAudio from "./static/StaticAudio.svelte";
10
  import InteractiveAudio from "./interactive/InteractiveAudio.svelte";
 
12
  import { Block, UploadText } from "@gradio/atoms";
13
  import type { WaveformOptions } from "./shared/types";
14
 
 
15
  export let elem_id = "";
16
  export let elem_classes: string[] = [];
17
  export let visible = true;
 
30
  export let min_width: number | undefined = undefined;
31
  export let loading_status: LoadingStatus;
32
  export let autoplay = false;
 
33
  export let show_download_button: boolean;
34
  export let show_share_button = false;
35
  export let editable = true;
36
  export let waveform_options: WaveformOptions = {};
37
  export let pending: boolean;
38
  export let streaming: boolean;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  export let gradio: Gradio<{
 
40
  change: typeof value;
41
  stream: typeof value;
42
  error: string;
 
52
  upload: never;
53
  clear: never;
54
  share: ShareData;
 
 
55
  }>;
56
 
57
  let old_value: null | FileData = null;
 
76
  if (JSON.stringify(value) !== JSON.stringify(old_value)) {
77
  old_value = value;
78
  gradio.dispatch("change");
 
 
 
79
  }
80
  }
81
 
 
87
 
88
  let waveform_settings: Record<string, any>;
89
 
90
+ let color_accent = getComputedStyle(
91
+ document.documentElement
92
+ ).getPropertyValue("--color-accent");
 
 
 
 
 
 
 
 
 
 
93
 
94
  $: waveform_settings = {
95
  height: 50,
96
+ waveColor: waveform_options.waveform_color || "#9ca3af",
97
+ progressColor: waveform_options.waveform_progress_color || color_accent,
98
  barWidth: 2,
99
  barGap: 3,
100
  cursorWidth: 2,
 
103
  barRadius: 10,
104
  dragToSeek: true,
105
  normalize: true,
106
+ minPxPerSec: 20,
107
+ mediaControls: waveform_options.show_controls,
108
+ sampleRate: waveform_options.sample_rate || 44100
109
  };
110
 
111
  const trim_region_settings = {
 
121
  );
122
  }
123
 
124
+ set_trim_region_colour();
125
+
126
  function handle_error({ detail }: CustomEvent<string>): void {
127
  const [level, status] = detail.includes("Invalid file type")
128
  ? ["warning", "complete"]
 
132
  loading_status.message = detail;
133
  gradio.dispatch(level as "error" | "warning", detail);
134
  }
 
 
 
 
135
  </script>
136
 
137
  {#if !interactive}
 
151
  autoscroll={gradio.autoscroll}
152
  i18n={gradio.i18n}
153
  {...loading_status}
 
154
  />
155
 
156
  <StaticAudio
 
160
  {show_share_button}
161
  {value}
162
  {label}
 
163
  {waveform_settings}
164
  {waveform_options}
165
  {editable}
 
187
  autoscroll={gradio.autoscroll}
188
  i18n={gradio.i18n}
189
  {...loading_status}
 
190
  />
191
  <InteractiveAudio
192
  {label}
 
204
  {active_source}
205
  {pending}
206
  {streaming}
 
 
 
207
  {handle_reset_value}
208
  {editable}
209
  bind:dragging
 
210
  on:edit={() => gradio.dispatch("edit")}
211
  on:play={() => gradio.dispatch("play")}
212
  on:pause={() => gradio.dispatch("pause")}
 
217
  on:upload={() => gradio.dispatch("upload")}
218
  on:clear={() => gradio.dispatch("clear")}
219
  on:error={handle_error}
 
220
  i18n={gradio.i18n}
221
  {waveform_settings}
222
  {waveform_options}
223
  {trim_region_settings}
 
 
 
 
 
224
  >
225
  <UploadText i18n={gradio.i18n} type="audio" />
226
  </InteractiveAudio>
sourceviewer/frontend/gradio.config.js DELETED
@@ -1,9 +0,0 @@
1
- export default {
2
- plugins: [],
3
- svelte: {
4
- preprocess: [],
5
- },
6
- build: {
7
- target: "modules",
8
- },
9
- };
 
 
 
 
 
 
 
 
 
 
sourceviewer/frontend/interactive/InteractiveAudio.svelte CHANGED
@@ -1,14 +1,18 @@
1
  <script lang="ts">
2
- import { onDestroy, createEventDispatcher, tick } from "svelte";
3
  import { Upload, ModifyUpload } from "@gradio/upload";
4
- import { prepare_files, type FileData, type Client } from "@gradio/client";
 
 
 
 
 
5
  import { BlockLabel } from "@gradio/atoms";
6
  import { Music } from "@gradio/icons";
7
- import { StreamingBar } from "@gradio/statustracker";
8
  import AudioPlayer from "../player/AudioPlayer.svelte";
9
 
10
  import type { IBlobEvent, IMediaRecorder } from "extendable-media-recorder";
11
- import type { I18nFormatter } from "js/core/src/gradio_helper";
12
  import AudioRecorder from "../recorder/AudioRecorder.svelte";
13
  import StreamAudio from "../streaming/StreamAudio.svelte";
14
  import { SelectSource } from "@gradio/atoms";
@@ -17,7 +21,6 @@
17
  export let value: null | FileData = null;
18
  export let label: string;
19
  export let root: string;
20
- export let loop: boolean;
21
  export let show_label = true;
22
  export let show_download_button = false;
23
  export let sources:
@@ -35,37 +38,15 @@
35
  export let active_source: "microphone" | "upload";
36
  export let handle_reset_value: () => void = () => {};
37
  export let editable = true;
38
- export let max_file_size: number | null = null;
39
- export let upload: Client["upload"];
40
- export let stream_handler: Client["stream"];
41
- export let stream_every: number;
42
- export let uploading = false;
43
- export let recording = false;
44
 
45
- let time_limit: number | null = null;
46
- let stream_state: "open" | "waiting" | "closed" = "closed";
47
-
48
- export const modify_stream: (state: "open" | "closed" | "waiting") => void = (
49
- state: "open" | "closed" | "waiting"
50
- ) => {
51
- if (state === "closed") {
52
- time_limit = null;
53
- stream_state = "closed";
54
- } else if (state === "waiting") {
55
- stream_state = "waiting";
56
- } else {
57
- stream_state = "open";
58
- }
59
- };
60
-
61
- export const set_time_limit = (time: number): void => {
62
- if (recording) time_limit = time;
63
- };
64
 
65
  $: dispatch("drag", dragging);
66
 
67
  // TODO: make use of this
68
  // export let type: "normal" | "numpy" = "normal";
 
69
  let recorder: IMediaRecorder;
70
  let mode = "";
71
  let header: Uint8Array | undefined = undefined;
@@ -73,6 +54,7 @@
73
  let submit_pending_stream_on_pending_end = false;
74
  let inited = false;
75
 
 
76
  const NUM_HEADER_BYTES = 44;
77
  let audio_chunks: Blob[] = [];
78
  let module_promises: [
@@ -87,8 +69,7 @@
87
  ];
88
  }
89
 
90
- const is_browser = typeof window !== "undefined";
91
- if (is_browser && streaming) {
92
  get_modules();
93
  }
94
 
@@ -107,7 +88,6 @@
107
  start_recording: undefined;
108
  pause_recording: undefined;
109
  stop_recording: undefined;
110
- close_stream: undefined;
111
  }>();
112
 
113
  const dispatch_blob = async (
@@ -117,10 +97,11 @@
117
  let _audio_blob = new File(blobs, "audio.wav");
118
  const val = await prepare_files([_audio_blob], event === "stream");
119
  value = (
120
- (await upload(val, root, undefined, max_file_size || undefined))?.filter(
121
  Boolean
122
  ) as FileData[]
123
  )[0];
 
124
  dispatch(event, value);
125
  };
126
 
@@ -147,10 +128,10 @@
147
  throw err;
148
  }
149
  if (stream == null) return;
150
-
151
  if (streaming) {
152
- const [{ MediaRecorder, register }, { connect }] =
153
- await Promise.all(module_promises);
 
154
  await register(await connect());
155
  recorder = new MediaRecorder(stream, { mimeType: "audio/wav" });
156
  recorder.addEventListener("dataavailable", handle_chunk);
@@ -159,14 +140,13 @@
159
  recorder.addEventListener("dataavailable", (event) => {
160
  audio_chunks.push(event.data);
161
  });
 
 
 
 
 
 
162
  }
163
- recorder.addEventListener("stop", async () => {
164
- recording = false;
165
- // recorder.stop();
166
- await dispatch_blob(audio_chunks, "change");
167
- await dispatch_blob(audio_chunks, "stop_recording");
168
- audio_chunks = [];
169
- });
170
  inited = true;
171
  }
172
 
@@ -181,7 +161,6 @@
181
  pending_stream.push(payload);
182
  } else {
183
  let blobParts = [header].concat(pending_stream, [payload]);
184
- if (!recording || stream_state === "waiting") return;
185
  dispatch_blob(blobParts, "stream");
186
  pending_stream = [];
187
  }
@@ -201,8 +180,8 @@
201
  dispatch("start_recording");
202
  if (!inited) await prepare_audio();
203
  header = undefined;
204
- if (streaming && recorder.state != "recording") {
205
- recorder.start(stream_every * 1000);
206
  }
207
  }
208
 
@@ -219,14 +198,12 @@
219
  dispatch("upload", detail);
220
  }
221
 
222
- async function stop(): Promise<void> {
223
  recording = false;
224
 
225
  if (streaming) {
226
- dispatch("close_stream");
227
  dispatch("stop_recording");
228
  recorder.stop();
229
-
230
  if (pending) {
231
  submit_pending_stream_on_pending_end = true;
232
  }
@@ -235,9 +212,6 @@
235
  mode = "";
236
  }
237
  }
238
-
239
- $: if (!recording && recorder) stop();
240
- $: if (recording && recorder) record();
241
  </script>
242
 
243
  <BlockLabel
@@ -247,10 +221,9 @@
247
  label={label || i18n("audio.audio")}
248
  />
249
  <div class="audio-container">
250
- <StreamingBar {time_limit} />
251
  {#if value === null || streaming}
252
  {#if active_source === "microphone"}
253
- <ModifyUpload {i18n} on:clear={clear} />
254
  {#if streaming}
255
  <StreamAudio
256
  {record}
@@ -259,14 +232,12 @@
259
  {i18n}
260
  {waveform_settings}
261
  {waveform_options}
262
- waiting={stream_state === "waiting"}
263
  />
264
  {:else}
265
  <AudioRecorder
266
  bind:mode
267
  {i18n}
268
  {editable}
269
- {recording}
270
  {dispatch_blob}
271
  {waveform_settings}
272
  {waveform_options}
@@ -282,12 +253,8 @@
282
  filetype="audio/aac,audio/midi,audio/mpeg,audio/ogg,audio/wav,audio/x-wav,audio/opus,audio/webm,audio/flac,audio/vnd.rn-realaudio,audio/x-ms-wma,audio/x-aiff,audio/amr,audio/*"
283
  on:load={handle_load}
284
  bind:dragging
285
- bind:uploading
286
  on:error={({ detail }) => dispatch("error", detail)}
287
  {root}
288
- {max_file_size}
289
- {upload}
290
- {stream_handler}
291
  >
292
  <slot />
293
  </Upload>
@@ -298,6 +265,7 @@
298
  on:clear={clear}
299
  on:edit={() => (mode = "edit")}
300
  download={show_download_button ? value.url : null}
 
301
  />
302
 
303
  <AudioPlayer
@@ -311,7 +279,6 @@
311
  {trim_region_settings}
312
  {handle_reset_value}
313
  {editable}
314
- {loop}
315
  interactive
316
  on:stop
317
  on:play
 
1
  <script lang="ts">
2
+ import { getContext, onDestroy, createEventDispatcher } from "svelte";
3
  import { Upload, ModifyUpload } from "@gradio/upload";
4
+ import {
5
+ upload,
6
+ prepare_files,
7
+ type FileData,
8
+ type upload_files
9
+ } from "@gradio/client";
10
  import { BlockLabel } from "@gradio/atoms";
11
  import { Music } from "@gradio/icons";
 
12
  import AudioPlayer from "../player/AudioPlayer.svelte";
13
 
14
  import type { IBlobEvent, IMediaRecorder } from "extendable-media-recorder";
15
+ import type { I18nFormatter } from "js/app/src/gradio_helper";
16
  import AudioRecorder from "../recorder/AudioRecorder.svelte";
17
  import StreamAudio from "../streaming/StreamAudio.svelte";
18
  import { SelectSource } from "@gradio/atoms";
 
21
  export let value: null | FileData = null;
22
  export let label: string;
23
  export let root: string;
 
24
  export let show_label = true;
25
  export let show_download_button = false;
26
  export let sources:
 
38
  export let active_source: "microphone" | "upload";
39
  export let handle_reset_value: () => void = () => {};
40
  export let editable = true;
 
 
 
 
 
 
41
 
42
+ // Needed for wasm support
43
+ const upload_fn = getContext<typeof upload_files>("upload_files");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  $: dispatch("drag", dragging);
46
 
47
  // TODO: make use of this
48
  // export let type: "normal" | "numpy" = "normal";
49
+ let recording = false;
50
  let recorder: IMediaRecorder;
51
  let mode = "";
52
  let header: Uint8Array | undefined = undefined;
 
54
  let submit_pending_stream_on_pending_end = false;
55
  let inited = false;
56
 
57
+ const STREAM_TIMESLICE = 500;
58
  const NUM_HEADER_BYTES = 44;
59
  let audio_chunks: Blob[] = [];
60
  let module_promises: [
 
69
  ];
70
  }
71
 
72
+ if (streaming) {
 
73
  get_modules();
74
  }
75
 
 
88
  start_recording: undefined;
89
  pause_recording: undefined;
90
  stop_recording: undefined;
 
91
  }>();
92
 
93
  const dispatch_blob = async (
 
97
  let _audio_blob = new File(blobs, "audio.wav");
98
  const val = await prepare_files([_audio_blob], event === "stream");
99
  value = (
100
+ (await upload(val, root, undefined, upload_fn))?.filter(
101
  Boolean
102
  ) as FileData[]
103
  )[0];
104
+
105
  dispatch(event, value);
106
  };
107
 
 
128
  throw err;
129
  }
130
  if (stream == null) return;
 
131
  if (streaming) {
132
+ const [{ MediaRecorder, register }, { connect }] = await Promise.all(
133
+ module_promises
134
+ );
135
  await register(await connect());
136
  recorder = new MediaRecorder(stream, { mimeType: "audio/wav" });
137
  recorder.addEventListener("dataavailable", handle_chunk);
 
140
  recorder.addEventListener("dataavailable", (event) => {
141
  audio_chunks.push(event.data);
142
  });
143
+ recorder.addEventListener("stop", async () => {
144
+ recording = false;
145
+ await dispatch_blob(audio_chunks, "change");
146
+ await dispatch_blob(audio_chunks, "stop_recording");
147
+ audio_chunks = [];
148
+ });
149
  }
 
 
 
 
 
 
 
150
  inited = true;
151
  }
152
 
 
161
  pending_stream.push(payload);
162
  } else {
163
  let blobParts = [header].concat(pending_stream, [payload]);
 
164
  dispatch_blob(blobParts, "stream");
165
  pending_stream = [];
166
  }
 
180
  dispatch("start_recording");
181
  if (!inited) await prepare_audio();
182
  header = undefined;
183
+ if (streaming) {
184
+ recorder.start(STREAM_TIMESLICE);
185
  }
186
  }
187
 
 
198
  dispatch("upload", detail);
199
  }
200
 
201
+ function stop(): void {
202
  recording = false;
203
 
204
  if (streaming) {
 
205
  dispatch("stop_recording");
206
  recorder.stop();
 
207
  if (pending) {
208
  submit_pending_stream_on_pending_end = true;
209
  }
 
212
  mode = "";
213
  }
214
  }
 
 
 
215
  </script>
216
 
217
  <BlockLabel
 
221
  label={label || i18n("audio.audio")}
222
  />
223
  <div class="audio-container">
 
224
  {#if value === null || streaming}
225
  {#if active_source === "microphone"}
226
+ <ModifyUpload {i18n} on:clear={clear} absolute={true} />
227
  {#if streaming}
228
  <StreamAudio
229
  {record}
 
232
  {i18n}
233
  {waveform_settings}
234
  {waveform_options}
 
235
  />
236
  {:else}
237
  <AudioRecorder
238
  bind:mode
239
  {i18n}
240
  {editable}
 
241
  {dispatch_blob}
242
  {waveform_settings}
243
  {waveform_options}
 
253
  filetype="audio/aac,audio/midi,audio/mpeg,audio/ogg,audio/wav,audio/x-wav,audio/opus,audio/webm,audio/flac,audio/vnd.rn-realaudio,audio/x-ms-wma,audio/x-aiff,audio/amr,audio/*"
254
  on:load={handle_load}
255
  bind:dragging
 
256
  on:error={({ detail }) => dispatch("error", detail)}
257
  {root}
 
 
 
258
  >
259
  <slot />
260
  </Upload>
 
265
  on:clear={clear}
266
  on:edit={() => (mode = "edit")}
267
  download={show_download_button ? value.url : null}
268
+ absolute={true}
269
  />
270
 
271
  <AudioPlayer
 
279
  {trim_region_settings}
280
  {handle_reset_value}
281
  {editable}
 
282
  interactive
283
  on:stop
284
  on:play
sourceviewer/frontend/package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
sourceviewer/frontend/package.json CHANGED
@@ -1,62 +1,33 @@
1
  {
2
  "name": "gradio_sourceviewer",
3
- "version": "0.14.8",
4
  "description": "Gradio UI packages",
5
  "type": "module",
6
  "author": "",
7
  "license": "ISC",
8
  "private": false,
9
  "dependencies": {
10
- "@gradio/atoms": "0.11.1",
11
- "@gradio/button": "0.3.7",
12
- "@gradio/client": "1.8.0",
13
- "@gradio/icons": "0.8.1",
14
- "@gradio/statustracker": "0.9.5",
15
- "@gradio/upload": "0.14.1",
16
- "@gradio/utils": "0.8.0",
17
- "@gradio/wasm": "0.15.0",
18
- "@types/wavesurfer.js": "^6.0.10",
19
  "extendable-media-recorder": "^9.0.0",
20
  "extendable-media-recorder-wav-encoder": "^7.0.76",
21
- "hls.js": "^1.5.13",
22
  "resize-observer-polyfill": "^1.5.1",
23
  "svelte-range-slider-pips": "^2.0.1",
24
- "wavesurfer.js": "^7.4.2"
25
- },
26
- "devDependencies": {
27
- "@gradio/preview": "0.13.0"
28
  },
29
  "main_changeset": true,
30
  "main": "index.ts",
31
  "exports": {
32
- "./package.json": "./package.json",
33
- ".": {
34
- "gradio": "./index.ts",
35
- "svelte": "./dist/index.js",
36
- "types": "./dist/index.d.ts"
37
- },
38
- "./example": {
39
- "gradio": "./Example.svelte",
40
- "svelte": "./dist/Example.svelte",
41
- "types": "./dist/Example.svelte.d.ts"
42
- },
43
- "./shared": {
44
- "gradio": "./shared/index.ts",
45
- "svelte": "./dist/shared/index.js",
46
- "types": "./dist/shared/index.d.ts"
47
- },
48
- "./base": {
49
- "gradio": "./static/StaticAudio.svelte",
50
- "svelte": "./dist/static/StaticAudio.svelte",
51
- "types": "./dist/static/StaticAudio.svelte.d.ts"
52
- }
53
- },
54
- "peerDependencies": {
55
- "svelte": "^4.0.0"
56
- },
57
- "repository": {
58
- "type": "git",
59
- "url": "git+https://github.com/gradio-app/gradio.git",
60
- "directory": "js/audio"
61
  }
62
  }
 
1
  {
2
  "name": "gradio_sourceviewer",
3
+ "version": "0.9.12",
4
  "description": "Gradio UI packages",
5
  "type": "module",
6
  "author": "",
7
  "license": "ISC",
8
  "private": false,
9
  "dependencies": {
10
+ "@gradio/atoms": "0.7.0",
11
+ "@gradio/button": "0.2.31",
12
+ "@gradio/client": "0.16.0",
13
+ "@gradio/icons": "0.4.0",
14
+ "@gradio/statustracker": "0.4.12",
15
+ "@gradio/upload": "0.8.5",
16
+ "@gradio/utils": "0.3.2",
17
+ "@gradio/wasm": "0.10.0",
 
18
  "extendable-media-recorder": "^9.0.0",
19
  "extendable-media-recorder-wav-encoder": "^7.0.76",
 
20
  "resize-observer-polyfill": "^1.5.1",
21
  "svelte-range-slider-pips": "^2.0.1",
22
+ "wavesurfer.js": "^7.4.2",
23
+ "@types/wavesurfer.js": "^6.0.10"
 
 
24
  },
25
  "main_changeset": true,
26
  "main": "index.ts",
27
  "exports": {
28
+ ".": "./index.ts",
29
+ "./example": "./Example.svelte",
30
+ "./shared": "./shared/index.ts",
31
+ "./package.json": "./package.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
  }
sourceviewer/frontend/player/AudioPlayer.svelte CHANGED
@@ -11,8 +11,6 @@
11
  import type { WaveformOptions } from "../shared/types";
12
  import { createEventDispatcher } from "svelte";
13
 
14
- import Hls from "hls.js";
15
-
16
  export let value: null | FileData = null;
17
  $: url = value?.url;
18
  export let label: string;
@@ -27,7 +25,6 @@
27
  export let waveform_settings: Record<string, any>;
28
  export let waveform_options: WaveformOptions;
29
  export let mode = "";
30
- export let loop: boolean;
31
  export let handle_reset_value: () => void = () => {};
32
 
33
  let container: HTMLDivElement;
@@ -41,9 +38,6 @@
41
  let trimDuration = 0;
42
 
43
  let show_volume_slider = false;
44
- let audio_player: HTMLAudioElement;
45
-
46
- let stream_active = false;
47
 
48
  const dispatch = createEventDispatcher<{
49
  stop: undefined;
@@ -51,7 +45,6 @@
51
  pause: undefined;
52
  edit: undefined;
53
  end: undefined;
54
- load: undefined;
55
  }>();
56
 
57
  const create_waveform = (): void => {
@@ -66,7 +59,7 @@
66
  });
67
  };
68
 
69
- $: if (!value?.is_stream && container !== undefined && container !== null) {
70
  if (waveform !== undefined) waveform.destroy();
71
  container.innerHTML = "";
72
  create_waveform();
@@ -93,12 +86,8 @@
93
  });
94
 
95
  $: waveform?.on("finish", () => {
96
- if (loop) {
97
- waveform?.play();
98
- } else {
99
- playing = false;
100
- dispatch("stop");
101
- }
102
  });
103
  $: waveform?.on("pause", () => {
104
  playing = false;
@@ -109,10 +98,6 @@
109
  dispatch("play");
110
  });
111
 
112
- $: waveform?.on("load", () => {
113
- dispatch("load");
114
- });
115
-
116
  const handle_trim_audio = async (
117
  start: number,
118
  end: number
@@ -134,7 +119,6 @@
134
  };
135
 
136
  async function load_audio(data: string): Promise<void> {
137
- stream_active = false;
138
  await resolve_wasm_src(data).then((resolved_src) => {
139
  if (!resolved_src || value?.is_stream) return;
140
  return waveform?.load(resolved_src);
@@ -143,52 +127,6 @@
143
 
144
  $: url && load_audio(url);
145
 
146
- function load_stream(value: FileData | null): void {
147
- if (!value || !value.is_stream || !value.url) return;
148
- if (!audio_player) return;
149
- if (Hls.isSupported() && !stream_active) {
150
- // Set config to start playback after 1 second of data received
151
- const hls = new Hls({
152
- maxBufferLength: 1,
153
- maxMaxBufferLength: 1,
154
- lowLatencyMode: true
155
- });
156
- hls.loadSource(value.url);
157
- hls.attachMedia(audio_player);
158
- hls.on(Hls.Events.MANIFEST_PARSED, function () {
159
- if (waveform_settings.autoplay) audio_player.play();
160
- });
161
- hls.on(Hls.Events.ERROR, function (event, data) {
162
- console.error("HLS error:", event, data);
163
- if (data.fatal) {
164
- switch (data.type) {
165
- case Hls.ErrorTypes.NETWORK_ERROR:
166
- console.error(
167
- "Fatal network error encountered, trying to recover"
168
- );
169
- hls.startLoad();
170
- break;
171
- case Hls.ErrorTypes.MEDIA_ERROR:
172
- console.error("Fatal media error encountered, trying to recover");
173
- hls.recoverMediaError();
174
- break;
175
- default:
176
- console.error("Fatal error, cannot recover");
177
- hls.destroy();
178
- break;
179
- }
180
- }
181
- });
182
- stream_active = true;
183
- } else if (!stream_active) {
184
- audio_player.src = value.url;
185
- if (waveform_settings.autoplay) audio_player.play();
186
- stream_active = true;
187
- }
188
- }
189
-
190
- $: load_stream(value);
191
-
192
  onMount(() => {
193
  window.addEventListener("keydown", (e) => {
194
  if (!waveform || show_volume_slider) return;
@@ -201,31 +139,24 @@
201
  });
202
  </script>
203
 
204
- <audio
205
- class="standard-player"
206
- class:hidden={!(value && value.is_stream)}
207
- controls
208
- autoplay={waveform_settings.autoplay}
209
- on:load
210
- bind:this={audio_player}
211
- on:ended={() => dispatch("stop")}
212
- on:play={() => dispatch("play")}
213
- />
214
  {#if value === null}
215
  <Empty size="small">
216
  <Music />
217
  </Empty>
218
- {:else if !value.is_stream}
 
 
 
 
 
 
 
219
  <div
220
  class="component-wrapper"
221
  data-testid={label ? "waveform-" + label : "unlabelled-audio"}
222
  >
223
  <div class="waveform-container">
224
- <div
225
- id="waveform"
226
- bind:this={container}
227
- style:height={container ? null : "58px"}
228
- />
229
  </div>
230
 
231
  <div class="timestamps">
@@ -238,25 +169,25 @@
238
  </div>
239
  </div>
240
 
241
- <!-- {#if waveform} -->
242
- <WaveformControls
243
- {container}
244
- {waveform}
245
- {playing}
246
- {audio_duration}
247
- {i18n}
248
- {interactive}
249
- {handle_trim_audio}
250
- bind:mode
251
- bind:trimDuration
252
- bind:show_volume_slider
253
- show_redo={interactive}
254
- {handle_reset_value}
255
- {waveform_options}
256
- {trim_region_settings}
257
- {editable}
258
- />
259
- <!-- {/if} -->
260
  </div>
261
  {/if}
262
 
@@ -307,8 +238,4 @@
307
  width: 100%;
308
  padding: var(--size-2);
309
  }
310
-
311
- .hidden {
312
- display: none;
313
- }
314
  </style>
 
11
  import type { WaveformOptions } from "../shared/types";
12
  import { createEventDispatcher } from "svelte";
13
 
 
 
14
  export let value: null | FileData = null;
15
  $: url = value?.url;
16
  export let label: string;
 
25
  export let waveform_settings: Record<string, any>;
26
  export let waveform_options: WaveformOptions;
27
  export let mode = "";
 
28
  export let handle_reset_value: () => void = () => {};
29
 
30
  let container: HTMLDivElement;
 
38
  let trimDuration = 0;
39
 
40
  let show_volume_slider = false;
 
 
 
41
 
42
  const dispatch = createEventDispatcher<{
43
  stop: undefined;
 
45
  pause: undefined;
46
  edit: undefined;
47
  end: undefined;
 
48
  }>();
49
 
50
  const create_waveform = (): void => {
 
59
  });
60
  };
61
 
62
+ $: if (container !== undefined) {
63
  if (waveform !== undefined) waveform.destroy();
64
  container.innerHTML = "";
65
  create_waveform();
 
86
  });
87
 
88
  $: waveform?.on("finish", () => {
89
+ playing = false;
90
+ dispatch("stop");
 
 
 
 
91
  });
92
  $: waveform?.on("pause", () => {
93
  playing = false;
 
98
  dispatch("play");
99
  });
100
 
 
 
 
 
101
  const handle_trim_audio = async (
102
  start: number,
103
  end: number
 
119
  };
120
 
121
  async function load_audio(data: string): Promise<void> {
 
122
  await resolve_wasm_src(data).then((resolved_src) => {
123
  if (!resolved_src || value?.is_stream) return;
124
  return waveform?.load(resolved_src);
 
127
 
128
  $: url && load_audio(url);
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  onMount(() => {
131
  window.addEventListener("keydown", (e) => {
132
  if (!waveform || show_volume_slider) return;
 
139
  });
140
  </script>
141
 
 
 
 
 
 
 
 
 
 
 
142
  {#if value === null}
143
  <Empty size="small">
144
  <Music />
145
  </Empty>
146
+ {:else if value.is_stream}
147
+ <audio
148
+ class="standard-player"
149
+ src={value.url}
150
+ controls
151
+ autoplay={waveform_settings.autoplay}
152
+ />
153
+ {:else}
154
  <div
155
  class="component-wrapper"
156
  data-testid={label ? "waveform-" + label : "unlabelled-audio"}
157
  >
158
  <div class="waveform-container">
159
+ <div id="waveform" bind:this={container} />
 
 
 
 
160
  </div>
161
 
162
  <div class="timestamps">
 
169
  </div>
170
  </div>
171
 
172
+ {#if waveform}
173
+ <WaveformControls
174
+ {container}
175
+ {waveform}
176
+ {playing}
177
+ {audio_duration}
178
+ {i18n}
179
+ {interactive}
180
+ {handle_trim_audio}
181
+ bind:mode
182
+ bind:trimDuration
183
+ bind:show_volume_slider
184
+ show_redo={interactive}
185
+ {handle_reset_value}
186
+ {waveform_options}
187
+ {trim_region_settings}
188
+ {editable}
189
+ />
190
+ {/if}
191
  </div>
192
  {/if}
193
 
 
238
  width: 100%;
239
  padding: var(--size-2);
240
  }
 
 
 
 
241
  </style>
sourceviewer/frontend/recorder/AudioRecorder.svelte CHANGED
@@ -23,7 +23,6 @@
23
  };
24
  export let handle_reset_value: () => void;
25
  export let editable = true;
26
- export let recording = false;
27
 
28
  let micWaveform: WaveSurfer;
29
  let recordingWaveform: WaveSurfer;
@@ -63,7 +62,7 @@
63
  edit: undefined;
64
  }>();
65
 
66
- function record_start_callback(): void {
67
  start_interval();
68
  timing = true;
69
  dispatch("start_recording");
@@ -71,9 +70,9 @@
71
  let waveformCanvas = microphoneContainer;
72
  if (waveformCanvas) waveformCanvas.style.display = "block";
73
  }
74
- }
75
 
76
- async function record_end_callback(blob: Blob): Promise<void> {
77
  seconds = 0;
78
  timing = false;
79
  clearInterval(interval);
@@ -92,7 +91,12 @@
92
  } catch (e) {
93
  console.error(e);
94
  }
95
- }
 
 
 
 
 
96
 
97
  $: record?.on("record-resume", () => {
98
  start_interval();
@@ -136,25 +140,6 @@
136
 
137
  record = micWaveform.registerPlugin(RecordPlugin.create());
138
  record.startMic();
139
- record?.on("record-end", record_end_callback);
140
- record?.on("record-start", record_start_callback);
141
- record?.on("record-pause", () => {
142
- dispatch("pause_recording");
143
- clearInterval(interval);
144
- });
145
-
146
- record?.on("record-end", (blob) => {
147
- recordedAudio = URL.createObjectURL(blob);
148
-
149
- const microphone = microphoneContainer;
150
- const recording = recordingContainer;
151
-
152
- if (microphone) microphone.style.display = "none";
153
- if (recording && recordedAudio) {
154
- recording.innerHTML = "";
155
- create_recording_waveform();
156
- }
157
- });
158
  };
159
 
160
  const create_recording_waveform = (): void => {
@@ -167,6 +152,19 @@
167
  });
168
  };
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  const handle_trim_audio = async (
171
  start: number,
172
  end: number
@@ -227,7 +225,6 @@
227
  bind:record
228
  {i18n}
229
  {timing}
230
- {recording}
231
  show_recording_waveform={waveform_options.show_recording_waveform}
232
  record_time={format_time(seconds)}
233
  />
 
23
  };
24
  export let handle_reset_value: () => void;
25
  export let editable = true;
 
26
 
27
  let micWaveform: WaveSurfer;
28
  let recordingWaveform: WaveSurfer;
 
62
  edit: undefined;
63
  }>();
64
 
65
+ $: record?.on("record-start", () => {
66
  start_interval();
67
  timing = true;
68
  dispatch("start_recording");
 
70
  let waveformCanvas = microphoneContainer;
71
  if (waveformCanvas) waveformCanvas.style.display = "block";
72
  }
73
+ });
74
 
75
+ $: record?.on("record-end", async (blob) => {
76
  seconds = 0;
77
  timing = false;
78
  clearInterval(interval);
 
91
  } catch (e) {
92
  console.error(e);
93
  }
94
+ });
95
+
96
+ $: record?.on("record-pause", () => {
97
+ dispatch("pause_recording");
98
+ clearInterval(interval);
99
+ });
100
 
101
  $: record?.on("record-resume", () => {
102
  start_interval();
 
140
 
141
  record = micWaveform.registerPlugin(RecordPlugin.create());
142
  record.startMic();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  };
144
 
145
  const create_recording_waveform = (): void => {
 
152
  });
153
  };
154
 
155
+ $: record?.on("record-end", (blob) => {
156
+ recordedAudio = URL.createObjectURL(blob);
157
+
158
+ const microphone = microphoneContainer;
159
+ const recording = recordingContainer;
160
+
161
+ if (microphone) microphone.style.display = "none";
162
+ if (recording && recordedAudio) {
163
+ recording.innerHTML = "";
164
+ create_recording_waveform();
165
+ }
166
+ });
167
+
168
  const handle_trim_audio = async (
169
  start: number,
170
  end: number
 
225
  bind:record
226
  {i18n}
227
  {timing}
 
228
  show_recording_waveform={waveform_options.show_recording_waveform}
229
  record_time={format_time(seconds)}
230
  />
sourceviewer/frontend/shared/DeviceSelect.svelte CHANGED
@@ -10,26 +10,24 @@
10
  error: string;
11
  }>();
12
 
13
- $: if (typeof window !== "undefined") {
14
- try {
15
- let tempDevices: MediaDeviceInfo[] = [];
16
- RecordPlugin.getAvailableAudioDevices().then(
17
- (devices: MediaDeviceInfo[]) => {
18
- micDevices = devices;
19
- devices.forEach((device) => {
20
- if (device.deviceId) {
21
- tempDevices.push(device);
22
- }
23
- });
24
- micDevices = tempDevices;
25
- }
26
- );
27
- } catch (err) {
28
- if (err instanceof DOMException && err.name == "NotAllowedError") {
29
- dispatch("error", i18n("audio.allow_recording_access"));
30
  }
31
- throw err;
 
 
 
32
  }
 
33
  }
34
  </script>
35
 
@@ -52,9 +50,9 @@
52
  height: var(--size-8);
53
  background: var(--block-background-fill);
54
  padding: 0px var(--spacing-xxl);
55
- border-radius: var(--button-large-radius);
56
  font-size: var(--text-md);
57
- border: 1px solid var(--block-border-color);
58
  gap: var(--size-1);
59
  }
60
 
 
10
  error: string;
11
  }>();
12
 
13
+ $: try {
14
+ let tempDevices: MediaDeviceInfo[] = [];
15
+ RecordPlugin.getAvailableAudioDevices().then(
16
+ (devices: MediaDeviceInfo[]) => {
17
+ micDevices = devices;
18
+ devices.forEach((device) => {
19
+ if (device.deviceId) {
20
+ tempDevices.push(device);
21
+ }
22
+ });
23
+ micDevices = tempDevices;
 
 
 
 
 
 
24
  }
25
+ );
26
+ } catch (err) {
27
+ if (err instanceof DOMException && err.name == "NotAllowedError") {
28
+ dispatch("error", i18n("audio.allow_recording_access"));
29
  }
30
+ throw err;
31
  }
32
  </script>
33
 
 
50
  height: var(--size-8);
51
  background: var(--block-background-fill);
52
  padding: 0px var(--spacing-xxl);
53
+ border-radius: var(--radius-full);
54
  font-size: var(--text-md);
55
+ border: 1px solid var(--neutral-400);
56
  gap: var(--size-1);
57
  }
58
 
sourceviewer/frontend/shared/VolumeControl.svelte CHANGED
@@ -4,7 +4,7 @@
4
 
5
  export let currentVolume = 1;
6
  export let show_volume_slider = false;
7
- export let waveform: WaveSurfer | undefined;
8
 
9
  let volumeElement: HTMLInputElement;
10
 
@@ -37,7 +37,7 @@
37
  on:input={(e) => {
38
  if (e.target instanceof HTMLInputElement) {
39
  currentVolume = parseFloat(e.target.value);
40
- waveform?.setVolume(currentVolume);
41
  }
42
  }}
43
  />
 
4
 
5
  export let currentVolume = 1;
6
  export let show_volume_slider = false;
7
+ export let waveform: WaveSurfer;
8
 
9
  let volumeElement: HTMLInputElement;
10
 
 
37
  on:input={(e) => {
38
  if (e.target instanceof HTMLInputElement) {
39
  currentVolume = parseFloat(e.target.value);
40
+ waveform.setVolume(currentVolume);
41
  }
42
  }}
43
  />
sourceviewer/frontend/shared/WaveformControls.svelte CHANGED
@@ -10,7 +10,7 @@
10
  import VolumeLevels from "./VolumeLevels.svelte";
11
  import VolumeControl from "./VolumeControl.svelte";
12
 
13
- export let waveform: WaveSurfer | undefined;
14
  export let audio_duration: number;
15
  export let i18n: I18nFormatter;
16
  export let playing: boolean;
@@ -30,7 +30,7 @@
30
  let playbackSpeeds = [0.5, 1, 1.5, 2];
31
  let playbackSpeed = playbackSpeeds[1];
32
 
33
- let trimRegion: RegionsPlugin | null = null;
34
  let activeRegion: Region | null = null;
35
 
36
  let leftRegionHandle: HTMLDivElement | null;
@@ -39,10 +39,7 @@
39
 
40
  let currentVolume = 1;
41
 
42
- $: trimRegion =
43
- container && waveform
44
- ? waveform.registerPlugin(RegionsPlugin.create())
45
- : null;
46
 
47
  $: trimRegion?.on("region-out", (region) => {
48
  region.play();
@@ -59,8 +56,7 @@
59
  });
60
 
61
  const addTrimRegion = (): void => {
62
- if (!trimRegion) return;
63
- activeRegion = trimRegion?.addRegion({
64
  start: audio_duration / 4,
65
  end: audio_duration / 2,
66
  ...trim_region_settings
@@ -194,7 +190,7 @@
194
  (playbackSpeeds.indexOf(playbackSpeed) + 1) % playbackSpeeds.length
195
  ];
196
 
197
- waveform?.setPlaybackRate(playbackSpeed);
198
  }}
199
  >
200
  <span>{playbackSpeed}x</span>
@@ -209,7 +205,7 @@
209
  waveform_options.skip_length
210
  )} seconds`}
211
  on:click={() =>
212
- waveform?.skip(
213
  get_skip_rewind_amount(audio_duration, waveform_options.skip_length) *
214
  -1
215
  )}
@@ -218,7 +214,7 @@
218
  </button>
219
  <button
220
  class="play-pause-button icon"
221
- on:click={() => waveform?.playPause()}
222
  aria-label={playing ? i18n("audio.pause") : i18n("audio.play")}
223
  >
224
  {#if playing}
@@ -234,7 +230,7 @@
234
  waveform_options.skip_length
235
  )} seconds"
236
  on:click={() =>
237
- waveform?.skip(
238
  get_skip_rewind_amount(audio_duration, waveform_options.skip_length)
239
  )}
240
  >
@@ -322,12 +318,7 @@
322
  grid-template-areas:
323
  "playback playback"
324
  "controls editing";
325
- }
326
- }
327
-
328
- @media (max-width: 319px) {
329
- .controls {
330
- overflow-x: scroll;
331
  }
332
  }
333
 
 
10
  import VolumeLevels from "./VolumeLevels.svelte";
11
  import VolumeControl from "./VolumeControl.svelte";
12
 
13
+ export let waveform: WaveSurfer;
14
  export let audio_duration: number;
15
  export let i18n: I18nFormatter;
16
  export let playing: boolean;
 
30
  let playbackSpeeds = [0.5, 1, 1.5, 2];
31
  let playbackSpeed = playbackSpeeds[1];
32
 
33
+ let trimRegion: RegionsPlugin;
34
  let activeRegion: Region | null = null;
35
 
36
  let leftRegionHandle: HTMLDivElement | null;
 
39
 
40
  let currentVolume = 1;
41
 
42
+ $: trimRegion = waveform.registerPlugin(RegionsPlugin.create());
 
 
 
43
 
44
  $: trimRegion?.on("region-out", (region) => {
45
  region.play();
 
56
  });
57
 
58
  const addTrimRegion = (): void => {
59
+ activeRegion = trimRegion.addRegion({
 
60
  start: audio_duration / 4,
61
  end: audio_duration / 2,
62
  ...trim_region_settings
 
190
  (playbackSpeeds.indexOf(playbackSpeed) + 1) % playbackSpeeds.length
191
  ];
192
 
193
+ waveform.setPlaybackRate(playbackSpeed);
194
  }}
195
  >
196
  <span>{playbackSpeed}x</span>
 
205
  waveform_options.skip_length
206
  )} seconds`}
207
  on:click={() =>
208
+ waveform.skip(
209
  get_skip_rewind_amount(audio_duration, waveform_options.skip_length) *
210
  -1
211
  )}
 
214
  </button>
215
  <button
216
  class="play-pause-button icon"
217
+ on:click={() => waveform.playPause()}
218
  aria-label={playing ? i18n("audio.pause") : i18n("audio.play")}
219
  >
220
  {#if playing}
 
230
  waveform_options.skip_length
231
  )} seconds"
232
  on:click={() =>
233
+ waveform.skip(
234
  get_skip_rewind_amount(audio_duration, waveform_options.skip_length)
235
  )}
236
  >
 
318
  grid-template-areas:
319
  "playback playback"
320
  "controls editing";
321
+ overflow: scroll;
 
 
 
 
 
322
  }
323
  }
324
 
sourceviewer/frontend/shared/WaveformRecordControls.svelte CHANGED
@@ -6,7 +6,6 @@
6
 
7
  export let record: RecordPlugin;
8
  export let i18n: I18nFormatter;
9
- export let recording = false;
10
 
11
  let micDevices: MediaDeviceInfo[] = [];
12
  let recordButton: HTMLButtonElement;
@@ -14,7 +13,6 @@
14
  let resumeButton: HTMLButtonElement;
15
  let stopButton: HTMLButtonElement;
16
  let stopButtonPaused: HTMLButtonElement;
17
- let recording_ongoing = false;
18
 
19
  export let record_time: string;
20
  export let show_recording_waveform: boolean | undefined;
@@ -55,14 +53,6 @@
55
  stopButton.style.display = "flex";
56
  stopButtonPaused.style.display = "none";
57
  });
58
-
59
- $: if (recording && !recording_ongoing) {
60
- record.startRecording();
61
- recording_ongoing = true;
62
- } else {
63
- record.stopRecording();
64
- recording_ongoing = false;
65
- }
66
  </script>
67
 
68
  <div class="controls">
@@ -141,9 +131,9 @@
141
  height: var(--size-8);
142
  width: var(--size-20);
143
  background-color: var(--block-background-fill);
144
- border-radius: var(--button-large-radius);
145
  align-items: center;
146
- border: 1px solid var(--block-border-color);
147
  margin: var(--size-1) var(--size-1) 0 0;
148
  }
149
 
@@ -170,7 +160,7 @@
170
  height: var(--size-8);
171
  width: var(--size-20);
172
  background-color: var(--block-background-fill);
173
- border-radius: var(--button-large-radius);
174
  align-items: center;
175
  border: 1px solid var(--primary-600);
176
  margin: var(--size-1) var(--size-1) 0 0;
@@ -189,10 +179,10 @@
189
  height: var(--size-8);
190
  width: var(--size-24);
191
  background-color: var(--block-background-fill);
192
- border-radius: var(--button-large-radius);
193
  display: flex;
194
  align-items: center;
195
- border: 1px solid var(--block-border-color);
196
  }
197
 
198
  .stop-button:disabled {
@@ -223,8 +213,8 @@
223
  display: none;
224
  height: var(--size-8);
225
  width: var(--size-20);
226
- border: 1px solid var(--block-border-color);
227
- border-radius: var(--button-large-radius);
228
  padding: var(--spacing-md);
229
  margin: var(--size-1) var(--size-1) 0 0;
230
  }
@@ -233,8 +223,8 @@
233
  display: none;
234
  height: var(--size-8);
235
  width: var(--size-20);
236
- border: 1px solid var(--block-border-color);
237
- border-radius: var(--button-large-radius);
238
  padding: var(--spacing-xl);
239
  line-height: 1px;
240
  font-size: var(--text-md);
@@ -245,7 +235,8 @@
245
  display: flex;
246
  height: var(--size-8);
247
  width: var(--size-20);
248
- border: 1px solid var(--block-border-color);
 
249
  padding: var(--spacing-md);
250
  align-items: center;
251
  justify-content: center;
 
6
 
7
  export let record: RecordPlugin;
8
  export let i18n: I18nFormatter;
 
9
 
10
  let micDevices: MediaDeviceInfo[] = [];
11
  let recordButton: HTMLButtonElement;
 
13
  let resumeButton: HTMLButtonElement;
14
  let stopButton: HTMLButtonElement;
15
  let stopButtonPaused: HTMLButtonElement;
 
16
 
17
  export let record_time: string;
18
  export let show_recording_waveform: boolean | undefined;
 
53
  stopButton.style.display = "flex";
54
  stopButtonPaused.style.display = "none";
55
  });
 
 
 
 
 
 
 
 
56
  </script>
57
 
58
  <div class="controls">
 
131
  height: var(--size-8);
132
  width: var(--size-20);
133
  background-color: var(--block-background-fill);
134
+ border-radius: var(--radius-3xl);
135
  align-items: center;
136
+ border: 1px solid var(--neutral-400);
137
  margin: var(--size-1) var(--size-1) 0 0;
138
  }
139
 
 
160
  height: var(--size-8);
161
  width: var(--size-20);
162
  background-color: var(--block-background-fill);
163
+ border-radius: var(--radius-3xl);
164
  align-items: center;
165
  border: 1px solid var(--primary-600);
166
  margin: var(--size-1) var(--size-1) 0 0;
 
179
  height: var(--size-8);
180
  width: var(--size-24);
181
  background-color: var(--block-background-fill);
182
+ border-radius: var(--radius-3xl);
183
  display: flex;
184
  align-items: center;
185
+ border: 1px solid var(--neutral-400);
186
  }
187
 
188
  .stop-button:disabled {
 
213
  display: none;
214
  height: var(--size-8);
215
  width: var(--size-20);
216
+ border: 1px solid var(--neutral-400);
217
+ border-radius: var(--radius-3xl);
218
  padding: var(--spacing-md);
219
  margin: var(--size-1) var(--size-1) 0 0;
220
  }
 
223
  display: none;
224
  height: var(--size-8);
225
  width: var(--size-20);
226
+ border: 1px solid var(--neutral-400);
227
+ border-radius: var(--radius-3xl);
228
  padding: var(--spacing-xl);
229
  line-height: 1px;
230
  font-size: var(--text-md);
 
235
  display: flex;
236
  height: var(--size-8);
237
  width: var(--size-20);
238
+ border: 1px solid var(--neutral-400);
239
+ border-radius: var(--radius-3xl);
240
  padding: var(--spacing-md);
241
  align-items: center;
242
  justify-content: center;
sourceviewer/frontend/static/StaticAudio.svelte CHANGED
@@ -1,12 +1,7 @@
1
  <script lang="ts">
2
  import { uploadToHuggingFace } from "@gradio/utils";
3
  import { Empty } from "@gradio/atoms";
4
- import {
5
- ShareButton,
6
- IconButton,
7
- BlockLabel,
8
- IconButtonWrapper
9
- } from "@gradio/atoms";
10
  import { Download, Music } from "@gradio/icons";
11
  import type { I18nFormatter } from "@gradio/utils";
12
  import AudioPlayer from "../player/AudioPlayer.svelte";
@@ -24,7 +19,6 @@
24
  export let waveform_settings: Record<string, any>;
25
  export let waveform_options: WaveformOptions;
26
  export let editable = true;
27
- export let loop: boolean;
28
 
29
  const dispatch = createEventDispatcher<{
30
  change: FileData;
@@ -45,14 +39,9 @@
45
  />
46
 
47
  {#if value !== null}
48
- <IconButtonWrapper>
49
  {#if show_download_button}
50
- <DownloadLink
51
- href={value.is_stream
52
- ? value.url?.replace("playlist.m3u8", "playlist-file")
53
- : value.url}
54
- download={value.orig_name || value.path}
55
- >
56
  <IconButton Icon={Download} label={i18n("common.download")} />
57
  </DownloadLink>
58
  {/if}
@@ -69,7 +58,7 @@
69
  {value}
70
  />
71
  {/if}
72
- </IconButtonWrapper>
73
 
74
  <AudioPlayer
75
  {value}
@@ -78,14 +67,22 @@
78
  {waveform_settings}
79
  {waveform_options}
80
  {editable}
81
- {loop}
82
  on:pause
83
  on:play
84
  on:stop
85
- on:load
86
  />
87
  {:else}
88
  <Empty size="small">
89
  <Music />
90
  </Empty>
91
  {/if}
 
 
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
  import { uploadToHuggingFace } from "@gradio/utils";
3
  import { Empty } from "@gradio/atoms";
4
+ import { ShareButton, IconButton, BlockLabel } from "@gradio/atoms";
 
 
 
 
 
5
  import { Download, Music } from "@gradio/icons";
6
  import type { I18nFormatter } from "@gradio/utils";
7
  import AudioPlayer from "../player/AudioPlayer.svelte";
 
19
  export let waveform_settings: Record<string, any>;
20
  export let waveform_options: WaveformOptions;
21
  export let editable = true;
 
22
 
23
  const dispatch = createEventDispatcher<{
24
  change: FileData;
 
39
  />
40
 
41
  {#if value !== null}
42
+ <div class="icon-buttons">
43
  {#if show_download_button}
44
+ <DownloadLink href={value.url} download={value.orig_name || value.path}>
 
 
 
 
 
45
  <IconButton Icon={Download} label={i18n("common.download")} />
46
  </DownloadLink>
47
  {/if}
 
58
  {value}
59
  />
60
  {/if}
61
+ </div>
62
 
63
  <AudioPlayer
64
  {value}
 
67
  {waveform_settings}
68
  {waveform_options}
69
  {editable}
 
70
  on:pause
71
  on:play
72
  on:stop
 
73
  />
74
  {:else}
75
  <Empty size="small">
76
  <Music />
77
  </Empty>
78
  {/if}
79
+
80
+ <style>
81
+ .icon-buttons {
82
+ display: flex;
83
+ position: absolute;
84
+ top: 6px;
85
+ right: 6px;
86
+ gap: var(--size-1);
87
+ }
88
+ </style>
sourceviewer/frontend/streaming/StreamAudio.svelte CHANGED
@@ -1,7 +1,6 @@
1
  <script lang="ts">
2
  import { onMount } from "svelte";
3
  import type { I18nFormatter } from "@gradio/utils";
4
- import { Spinner } from "@gradio/icons";
5
  import WaveSurfer from "wavesurfer.js";
6
  import RecordPlugin from "wavesurfer.js/dist/plugins/record.js";
7
  import type { WaveformOptions } from "../shared/types";
@@ -16,7 +15,6 @@
16
  export let waveform_options: WaveformOptions = {
17
  show_recording_waveform: true
18
  };
19
- export let waiting = false;
20
 
21
  let micWaveform: WaveSurfer;
22
  let waveformRecord: RecordPlugin;
@@ -50,7 +48,7 @@
50
  />
51
  {/if}
52
  <div class="controls">
53
- {#if recording && !waiting}
54
  <button
55
  class={paused_recording ? "stop-button-paused" : "stop-button"}
56
  on:click={() => {
@@ -64,18 +62,6 @@
64
  </span>
65
  {paused_recording ? i18n("audio.pause") : i18n("audio.stop")}
66
  </button>
67
- {:else if recording && waiting}
68
- <button
69
- class="spinner-button"
70
- on:click={() => {
71
- stop();
72
- }}
73
- >
74
- <div class="icon">
75
- <Spinner />
76
- </div>
77
- {i18n("audio.waiting")}
78
- </button>
79
  {:else}
80
  <button
81
  class="record-button"
@@ -109,21 +95,14 @@
109
  margin: var(--spacing-xl);
110
  }
111
 
112
- .icon {
113
- width: var(--size-4);
114
- height: var(--size-4);
115
- fill: var(--primary-600);
116
- stroke: var(--primary-600);
117
- }
118
-
119
  .stop-button-paused {
120
  display: none;
121
  height: var(--size-8);
122
  width: var(--size-20);
123
  background-color: var(--block-background-fill);
124
- border-radius: var(--button-large-radius);
125
  align-items: center;
126
- border: 1px solid var(--block-border-color);
127
  margin-right: 5px;
128
  }
129
 
@@ -150,23 +129,11 @@
150
  height: var(--size-8);
151
  width: var(--size-20);
152
  background-color: var(--block-background-fill);
153
- border-radius: var(--button-large-radius);
154
- align-items: center;
155
- border: 1px solid var(--primary-600);
156
- margin-right: 5px;
157
- display: flex;
158
- }
159
-
160
- .spinner-button {
161
- height: var(--size-8);
162
- width: var(--size-24);
163
- background-color: var(--block-background-fill);
164
  border-radius: var(--radius-3xl);
165
  align-items: center;
166
  border: 1px solid var(--primary-600);
167
- margin: 0 var(--spacing-xl);
168
  display: flex;
169
- justify-content: space-evenly;
170
  }
171
 
172
  .record-button::before {
@@ -182,10 +149,10 @@
182
  height: var(--size-8);
183
  width: var(--size-24);
184
  background-color: var(--block-background-fill);
185
- border-radius: var(--button-large-radius);
186
  display: flex;
187
  align-items: center;
188
- border: 1px solid var(--block-border-color);
189
  }
190
 
191
  @keyframes scaling {
 
1
  <script lang="ts">
2
  import { onMount } from "svelte";
3
  import type { I18nFormatter } from "@gradio/utils";
 
4
  import WaveSurfer from "wavesurfer.js";
5
  import RecordPlugin from "wavesurfer.js/dist/plugins/record.js";
6
  import type { WaveformOptions } from "../shared/types";
 
15
  export let waveform_options: WaveformOptions = {
16
  show_recording_waveform: true
17
  };
 
18
 
19
  let micWaveform: WaveSurfer;
20
  let waveformRecord: RecordPlugin;
 
48
  />
49
  {/if}
50
  <div class="controls">
51
+ {#if recording}
52
  <button
53
  class={paused_recording ? "stop-button-paused" : "stop-button"}
54
  on:click={() => {
 
62
  </span>
63
  {paused_recording ? i18n("audio.pause") : i18n("audio.stop")}
64
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
65
  {:else}
66
  <button
67
  class="record-button"
 
95
  margin: var(--spacing-xl);
96
  }
97
 
 
 
 
 
 
 
 
98
  .stop-button-paused {
99
  display: none;
100
  height: var(--size-8);
101
  width: var(--size-20);
102
  background-color: var(--block-background-fill);
103
+ border-radius: var(--radius-3xl);
104
  align-items: center;
105
+ border: 1px solid var(--neutral-400);
106
  margin-right: 5px;
107
  }
108
 
 
129
  height: var(--size-8);
130
  width: var(--size-20);
131
  background-color: var(--block-background-fill);
 
 
 
 
 
 
 
 
 
 
 
132
  border-radius: var(--radius-3xl);
133
  align-items: center;
134
  border: 1px solid var(--primary-600);
135
+ margin-right: 5px;
136
  display: flex;
 
137
  }
138
 
139
  .record-button::before {
 
149
  height: var(--size-8);
150
  width: var(--size-24);
151
  background-color: var(--block-background-fill);
152
+ border-radius: var(--radius-3xl);
153
  display: flex;
154
  align-items: center;
155
+ border: 1px solid var(--neutral-400);
156
  }
157
 
158
  @keyframes scaling {
sourceviewer/pyproject.toml CHANGED
@@ -19,9 +19,10 @@ keywords = [
19
  "gradio-template-Audio"
20
  ]
21
  # Add dependencies here
22
- dependencies = ["gradio>=4.0,<6.0"]
23
  classifiers = [
24
  'Development Status :: 3 - Alpha',
 
25
  'Operating System :: OS Independent',
26
  'Programming Language :: Python :: 3',
27
  'Programming Language :: Python :: 3 :: Only',
@@ -34,16 +35,6 @@ classifiers = [
34
  'Topic :: Scientific/Engineering :: Visualization',
35
  ]
36
 
37
- # The repository and space URLs are optional, but recommended.
38
- # Adding a repository URL will create a badge in the auto-generated README that links to the repository.
39
- # Adding a space URL will create a badge in the auto-generated README that links to the space.
40
- # This will make it easy for people to find your deployed demo or source code when they
41
- # encounter your project in the wild.
42
-
43
- # [project.urls]
44
- # repository = "your github repository"
45
- # space = "your space url"
46
-
47
  [project.optional-dependencies]
48
  dev = ["build", "twine"]
49
 
 
19
  "gradio-template-Audio"
20
  ]
21
  # Add dependencies here
22
+ dependencies = ["gradio>=4.0,<5.0"]
23
  classifiers = [
24
  'Development Status :: 3 - Alpha',
25
+ 'License :: OSI Approved :: Apache Software License',
26
  'Operating System :: OS Independent',
27
  'Programming Language :: Python :: 3',
28
  'Programming Language :: Python :: 3 :: Only',
 
35
  'Topic :: Scientific/Engineering :: Visualization',
36
  ]
37
 
 
 
 
 
 
 
 
 
 
 
38
  [project.optional-dependencies]
39
  dev = ["build", "twine"]
40