diff --git a/assets/examples/sample-audio.mp3 b/assets/examples/sample-audio.mp3 new file mode 100644 index 00000000000..318929a9bb2 Binary files /dev/null and b/assets/examples/sample-audio.mp3 differ diff --git a/py/examples/audio_annotator.py b/py/examples/audio_annotator.py new file mode 100644 index 00000000000..5f26aeb73f4 --- /dev/null +++ b/py/examples/audio_annotator.py @@ -0,0 +1,35 @@ +# Form / Audio Annotator +# Use when you need to annotate audio. +# #form #annotator #audio +# --- +from h2o_wave import main, app, Q, ui +import os + + +@app('/demo') +async def serve(q: Q): + # Upload the audio file to Wave server first. + if not q.app.initialized: + example_dir = os.path.dirname(os.path.realpath(__file__)) + q.app.uploaded_mp3, = await q.site.upload([os.path.join(example_dir, 'audio_annotator_sample.mp3')]) + q.app.initialized = True + + if q.args.annotator is not None: + q.page['example'].items = [ + ui.text(f'annotator={q.args.annotator}'), + ui.button(name='back', label='Back', primary=True), + ] + else: + q.page['example'] = ui.form_card(box='1 1 7 -1', items=[ + ui.audio_annotator( + name='annotator', + title='Drag to annotate', + path=q.app.uploaded_mp3, + tags=[ + ui.audio_annotator_tag(name='f', label='Flute', color='$blue'), + ui.audio_annotator_tag(name='d', label='Drum', color='$brown'), + ], + ), + ui.button(name='submit', label='Submit', primary=True) + ]) + await q.page.save() diff --git a/py/examples/audio_annotator_sample.mp3 b/py/examples/audio_annotator_sample.mp3 new file mode 100644 index 00000000000..a28b3550e85 Binary files /dev/null and b/py/examples/audio_annotator_sample.mp3 differ diff --git a/py/examples/tour.conf b/py/examples/tour.conf index c5d2ab3f8ad..c9f8eed7566 100644 --- a/py/examples/tour.conf +++ b/py/examples/tour.conf @@ -111,6 +111,7 @@ image_popup.py image_annotator.py image_annotator_events_click.py image_annotator_events_tool_change.py +audio_annotator.py inline.py file_stream.py frame.py diff --git a/py/h2o_lightwave/h2o_lightwave/types.py b/py/h2o_lightwave/h2o_lightwave/types.py index bb64323923b..23b57b310c8 100644 --- a/py/h2o_lightwave/h2o_lightwave/types.py +++ b/py/h2o_lightwave/h2o_lightwave/types.py @@ -6736,7 +6736,7 @@ def __init__( self.allowed_shapes = allowed_shapes """List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default.""" self.events = events - """The events to capture on this image annotator. One of `click` or `tool_change`.""" + """The events to capture on this image annotator. One of `click` | `tool_change`.""" def dump(self) -> Dict: """Returns the contents of this object as a dict.""" @@ -6804,6 +6804,185 @@ def load(__d: Dict) -> 'ImageAnnotator': ) +class AudioAnnotatorTag: + """Create a unique tag type for use in an audio annotator. + """ + def __init__( + self, + name: str, + label: str, + color: str, + ): + _guard_scalar('AudioAnnotatorTag.name', name, (str,), True, False, False) + _guard_scalar('AudioAnnotatorTag.label', label, (str,), False, False, False) + _guard_scalar('AudioAnnotatorTag.color', color, (str,), False, False, False) + self.name = name + """An identifying name for this tag.""" + self.label = label + """Text to be displayed for the annotation.""" + self.color = color + """Hex or RGB color string to be used as the background color.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotatorTag.name', self.name, (str,), True, False, False) + _guard_scalar('AudioAnnotatorTag.label', self.label, (str,), False, False, False) + _guard_scalar('AudioAnnotatorTag.color', self.color, (str,), False, False, False) + return _dump( + name=self.name, + label=self.label, + color=self.color, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotatorTag': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('AudioAnnotatorTag.name', __d_name, (str,), True, False, False) + __d_label: Any = __d.get('label') + _guard_scalar('AudioAnnotatorTag.label', __d_label, (str,), False, False, False) + __d_color: Any = __d.get('color') + _guard_scalar('AudioAnnotatorTag.color', __d_color, (str,), False, False, False) + name: str = __d_name + label: str = __d_label + color: str = __d_color + return AudioAnnotatorTag( + name, + label, + color, + ) + + +class AudioAnnotatorItem: + """Create an annotator item with initial selected tags or no tags. + """ + def __init__( + self, + start: float, + end: float, + tag: str, + ): + _guard_scalar('AudioAnnotatorItem.start', start, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.end', end, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.tag', tag, (str,), False, False, False) + self.start = start + """The start of the audio annotation in seconds.""" + self.end = end + """The end of the audio annotation in seconds.""" + self.tag = tag + """The `name` of the audio annotator tag to refer to for the `label` and `color` of this item.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotatorItem.start', self.start, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.end', self.end, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.tag', self.tag, (str,), False, False, False) + return _dump( + start=self.start, + end=self.end, + tag=self.tag, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotatorItem': + """Creates an instance of this class using the contents of a dict.""" + __d_start: Any = __d.get('start') + _guard_scalar('AudioAnnotatorItem.start', __d_start, (float, int,), False, False, False) + __d_end: Any = __d.get('end') + _guard_scalar('AudioAnnotatorItem.end', __d_end, (float, int,), False, False, False) + __d_tag: Any = __d.get('tag') + _guard_scalar('AudioAnnotatorItem.tag', __d_tag, (str,), False, False, False) + start: float = __d_start + end: float = __d_end + tag: str = __d_tag + return AudioAnnotatorItem( + start, + end, + tag, + ) + + +class AudioAnnotator: + """Create an audio annotator component. + + This component allows annotating and labeling parts of audio file. + """ + def __init__( + self, + name: str, + title: str, + path: str, + tags: List[AudioAnnotatorTag], + items: Optional[List[AudioAnnotatorItem]] = None, + trigger: Optional[bool] = None, + ): + _guard_scalar('AudioAnnotator.name', name, (str,), True, False, False) + _guard_scalar('AudioAnnotator.title', title, (str,), False, False, False) + _guard_scalar('AudioAnnotator.path', path, (str,), False, False, False) + _guard_vector('AudioAnnotator.tags', tags, (AudioAnnotatorTag,), False, False, False) + _guard_vector('AudioAnnotator.items', items, (AudioAnnotatorItem,), False, True, False) + _guard_scalar('AudioAnnotator.trigger', trigger, (bool,), False, True, False) + self.name = name + """An identifying name for this component.""" + self.title = title + """The audio annotator's title.""" + self.path = path + """The path to the audio file. Use mp3 or wav formats to achieve the best cross-browser support. See https://caniuse.com/?search=audio%20format for other formats.""" + self.tags = tags + """The master list of tags that can be used for annotations.""" + self.items = items + """Annotations to display on the image, if any.""" + self.trigger = trigger + """True if the form should be submitted as soon as an annotation is made.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotator.name', self.name, (str,), True, False, False) + _guard_scalar('AudioAnnotator.title', self.title, (str,), False, False, False) + _guard_scalar('AudioAnnotator.path', self.path, (str,), False, False, False) + _guard_vector('AudioAnnotator.tags', self.tags, (AudioAnnotatorTag,), False, False, False) + _guard_vector('AudioAnnotator.items', self.items, (AudioAnnotatorItem,), False, True, False) + _guard_scalar('AudioAnnotator.trigger', self.trigger, (bool,), False, True, False) + return _dump( + name=self.name, + title=self.title, + path=self.path, + tags=[__e.dump() for __e in self.tags], + items=None if self.items is None else [__e.dump() for __e in self.items], + trigger=self.trigger, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotator': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('AudioAnnotator.name', __d_name, (str,), True, False, False) + __d_title: Any = __d.get('title') + _guard_scalar('AudioAnnotator.title', __d_title, (str,), False, False, False) + __d_path: Any = __d.get('path') + _guard_scalar('AudioAnnotator.path', __d_path, (str,), False, False, False) + __d_tags: Any = __d.get('tags') + _guard_vector('AudioAnnotator.tags', __d_tags, (dict,), False, False, False) + __d_items: Any = __d.get('items') + _guard_vector('AudioAnnotator.items', __d_items, (dict,), False, True, False) + __d_trigger: Any = __d.get('trigger') + _guard_scalar('AudioAnnotator.trigger', __d_trigger, (bool,), False, True, False) + name: str = __d_name + title: str = __d_title + path: str = __d_path + tags: List[AudioAnnotatorTag] = [AudioAnnotatorTag.load(__e) for __e in __d_tags] + items: Optional[List[AudioAnnotatorItem]] = None if __d_items is None else [AudioAnnotatorItem.load(__e) for __e in __d_items] + trigger: Optional[bool] = __d_trigger + return AudioAnnotator( + name, + title, + path, + tags, + items, + trigger, + ) + + class Facepile: """A face pile displays a list of personas. Each circle represents a person and contains their image or initials. Often this control is used when sharing who has access to a specific view or file. @@ -7233,6 +7412,7 @@ def __init__( persona: Optional[Persona] = None, text_annotator: Optional[TextAnnotator] = None, image_annotator: Optional[ImageAnnotator] = None, + audio_annotator: Optional[AudioAnnotator] = None, facepile: Optional[Facepile] = None, copyable_text: Optional[CopyableText] = None, menu: Optional[Menu] = None, @@ -7284,6 +7464,7 @@ def __init__( _guard_scalar('Component.persona', persona, (Persona,), False, True, False) _guard_scalar('Component.text_annotator', text_annotator, (TextAnnotator,), False, True, False) _guard_scalar('Component.image_annotator', image_annotator, (ImageAnnotator,), False, True, False) + _guard_scalar('Component.audio_annotator', audio_annotator, (AudioAnnotator,), False, True, False) _guard_scalar('Component.facepile', facepile, (Facepile,), False, True, False) _guard_scalar('Component.copyable_text', copyable_text, (CopyableText,), False, True, False) _guard_scalar('Component.menu', menu, (Menu,), False, True, False) @@ -7379,6 +7560,8 @@ def __init__( """Text annotator.""" self.image_annotator = image_annotator """Image annotator.""" + self.audio_annotator = audio_annotator + """Audio annotator.""" self.facepile = facepile """Facepile.""" self.copyable_text = copyable_text @@ -7437,6 +7620,7 @@ def dump(self) -> Dict: _guard_scalar('Component.persona', self.persona, (Persona,), False, True, False) _guard_scalar('Component.text_annotator', self.text_annotator, (TextAnnotator,), False, True, False) _guard_scalar('Component.image_annotator', self.image_annotator, (ImageAnnotator,), False, True, False) + _guard_scalar('Component.audio_annotator', self.audio_annotator, (AudioAnnotator,), False, True, False) _guard_scalar('Component.facepile', self.facepile, (Facepile,), False, True, False) _guard_scalar('Component.copyable_text', self.copyable_text, (CopyableText,), False, True, False) _guard_scalar('Component.menu', self.menu, (Menu,), False, True, False) @@ -7488,6 +7672,7 @@ def dump(self) -> Dict: persona=None if self.persona is None else self.persona.dump(), text_annotator=None if self.text_annotator is None else self.text_annotator.dump(), image_annotator=None if self.image_annotator is None else self.image_annotator.dump(), + audio_annotator=None if self.audio_annotator is None else self.audio_annotator.dump(), facepile=None if self.facepile is None else self.facepile.dump(), copyable_text=None if self.copyable_text is None else self.copyable_text.dump(), menu=None if self.menu is None else self.menu.dump(), @@ -7588,6 +7773,8 @@ def load(__d: Dict) -> 'Component': _guard_scalar('Component.text_annotator', __d_text_annotator, (dict,), False, True, False) __d_image_annotator: Any = __d.get('image_annotator') _guard_scalar('Component.image_annotator', __d_image_annotator, (dict,), False, True, False) + __d_audio_annotator: Any = __d.get('audio_annotator') + _guard_scalar('Component.audio_annotator', __d_audio_annotator, (dict,), False, True, False) __d_facepile: Any = __d.get('facepile') _guard_scalar('Component.facepile', __d_facepile, (dict,), False, True, False) __d_copyable_text: Any = __d.get('copyable_text') @@ -7643,6 +7830,7 @@ def load(__d: Dict) -> 'Component': persona: Optional[Persona] = None if __d_persona is None else Persona.load(__d_persona) text_annotator: Optional[TextAnnotator] = None if __d_text_annotator is None else TextAnnotator.load(__d_text_annotator) image_annotator: Optional[ImageAnnotator] = None if __d_image_annotator is None else ImageAnnotator.load(__d_image_annotator) + audio_annotator: Optional[AudioAnnotator] = None if __d_audio_annotator is None else AudioAnnotator.load(__d_audio_annotator) facepile: Optional[Facepile] = None if __d_facepile is None else Facepile.load(__d_facepile) copyable_text: Optional[CopyableText] = None if __d_copyable_text is None else CopyableText.load(__d_copyable_text) menu: Optional[Menu] = None if __d_menu is None else Menu.load(__d_menu) @@ -7694,6 +7882,7 @@ def load(__d: Dict) -> 'Component': persona, text_annotator, image_annotator, + audio_annotator, facepile, copyable_text, menu, diff --git a/py/h2o_lightwave/h2o_lightwave/ui.py b/py/h2o_lightwave/h2o_lightwave/ui.py index 6c3098190c1..39e08ed4dca 100644 --- a/py/h2o_lightwave/h2o_lightwave/ui.py +++ b/py/h2o_lightwave/h2o_lightwave/ui.py @@ -2497,7 +2497,7 @@ def image_annotator( trigger: True if the form should be submitted as soon as an annotation is drawn. image_height: The card’s image height. The actual image size is used by default. allowed_shapes: List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default. - events: The events to capture on this image annotator. One of `click` or `tool_change`. + events: The events to capture on this image annotator. One of `click` | `tool_change`. Returns: A `h2o_wave.types.ImageAnnotator` instance. """ @@ -2514,6 +2514,80 @@ def image_annotator( )) +def audio_annotator_tag( + name: str, + label: str, + color: str, +) -> AudioAnnotatorTag: + """Create a unique tag type for use in an audio annotator. + + Args: + name: An identifying name for this tag. + label: Text to be displayed for the annotation. + color: Hex or RGB color string to be used as the background color. + Returns: + A `h2o_wave.types.AudioAnnotatorTag` instance. + """ + return AudioAnnotatorTag( + name, + label, + color, + ) + + +def audio_annotator_item( + start: float, + end: float, + tag: str, +) -> AudioAnnotatorItem: + """Create an annotator item with initial selected tags or no tags. + + Args: + start: The start of the audio annotation in seconds. + end: The end of the audio annotation in seconds. + tag: The `name` of the audio annotator tag to refer to for the `label` and `color` of this item. + Returns: + A `h2o_wave.types.AudioAnnotatorItem` instance. + """ + return AudioAnnotatorItem( + start, + end, + tag, + ) + + +def audio_annotator( + name: str, + title: str, + path: str, + tags: List[AudioAnnotatorTag], + items: Optional[List[AudioAnnotatorItem]] = None, + trigger: Optional[bool] = None, +) -> Component: + """Create an audio annotator component. + + This component allows annotating and labeling parts of audio file. + + Args: + name: An identifying name for this component. + title: The audio annotator's title. + path: The path to the audio file. Use mp3 or wav formats to achieve the best cross-browser support. See https://caniuse.com/?search=audio%20format for other formats. + tags: The master list of tags that can be used for annotations. + items: Annotations to display on the image, if any. + trigger: True if the form should be submitted as soon as an annotation is made. + Returns: + A `h2o_wave.types.AudioAnnotator` instance. + """ + return Component(audio_annotator=AudioAnnotator( + name, + title, + path, + tags, + items, + trigger, + )) + + def facepile( items: List[Component], name: Optional[str] = None, diff --git a/py/h2o_wave/h2o_wave/types.py b/py/h2o_wave/h2o_wave/types.py index bb64323923b..23b57b310c8 100644 --- a/py/h2o_wave/h2o_wave/types.py +++ b/py/h2o_wave/h2o_wave/types.py @@ -6736,7 +6736,7 @@ def __init__( self.allowed_shapes = allowed_shapes """List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default.""" self.events = events - """The events to capture on this image annotator. One of `click` or `tool_change`.""" + """The events to capture on this image annotator. One of `click` | `tool_change`.""" def dump(self) -> Dict: """Returns the contents of this object as a dict.""" @@ -6804,6 +6804,185 @@ def load(__d: Dict) -> 'ImageAnnotator': ) +class AudioAnnotatorTag: + """Create a unique tag type for use in an audio annotator. + """ + def __init__( + self, + name: str, + label: str, + color: str, + ): + _guard_scalar('AudioAnnotatorTag.name', name, (str,), True, False, False) + _guard_scalar('AudioAnnotatorTag.label', label, (str,), False, False, False) + _guard_scalar('AudioAnnotatorTag.color', color, (str,), False, False, False) + self.name = name + """An identifying name for this tag.""" + self.label = label + """Text to be displayed for the annotation.""" + self.color = color + """Hex or RGB color string to be used as the background color.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotatorTag.name', self.name, (str,), True, False, False) + _guard_scalar('AudioAnnotatorTag.label', self.label, (str,), False, False, False) + _guard_scalar('AudioAnnotatorTag.color', self.color, (str,), False, False, False) + return _dump( + name=self.name, + label=self.label, + color=self.color, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotatorTag': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('AudioAnnotatorTag.name', __d_name, (str,), True, False, False) + __d_label: Any = __d.get('label') + _guard_scalar('AudioAnnotatorTag.label', __d_label, (str,), False, False, False) + __d_color: Any = __d.get('color') + _guard_scalar('AudioAnnotatorTag.color', __d_color, (str,), False, False, False) + name: str = __d_name + label: str = __d_label + color: str = __d_color + return AudioAnnotatorTag( + name, + label, + color, + ) + + +class AudioAnnotatorItem: + """Create an annotator item with initial selected tags or no tags. + """ + def __init__( + self, + start: float, + end: float, + tag: str, + ): + _guard_scalar('AudioAnnotatorItem.start', start, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.end', end, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.tag', tag, (str,), False, False, False) + self.start = start + """The start of the audio annotation in seconds.""" + self.end = end + """The end of the audio annotation in seconds.""" + self.tag = tag + """The `name` of the audio annotator tag to refer to for the `label` and `color` of this item.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotatorItem.start', self.start, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.end', self.end, (float, int,), False, False, False) + _guard_scalar('AudioAnnotatorItem.tag', self.tag, (str,), False, False, False) + return _dump( + start=self.start, + end=self.end, + tag=self.tag, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotatorItem': + """Creates an instance of this class using the contents of a dict.""" + __d_start: Any = __d.get('start') + _guard_scalar('AudioAnnotatorItem.start', __d_start, (float, int,), False, False, False) + __d_end: Any = __d.get('end') + _guard_scalar('AudioAnnotatorItem.end', __d_end, (float, int,), False, False, False) + __d_tag: Any = __d.get('tag') + _guard_scalar('AudioAnnotatorItem.tag', __d_tag, (str,), False, False, False) + start: float = __d_start + end: float = __d_end + tag: str = __d_tag + return AudioAnnotatorItem( + start, + end, + tag, + ) + + +class AudioAnnotator: + """Create an audio annotator component. + + This component allows annotating and labeling parts of audio file. + """ + def __init__( + self, + name: str, + title: str, + path: str, + tags: List[AudioAnnotatorTag], + items: Optional[List[AudioAnnotatorItem]] = None, + trigger: Optional[bool] = None, + ): + _guard_scalar('AudioAnnotator.name', name, (str,), True, False, False) + _guard_scalar('AudioAnnotator.title', title, (str,), False, False, False) + _guard_scalar('AudioAnnotator.path', path, (str,), False, False, False) + _guard_vector('AudioAnnotator.tags', tags, (AudioAnnotatorTag,), False, False, False) + _guard_vector('AudioAnnotator.items', items, (AudioAnnotatorItem,), False, True, False) + _guard_scalar('AudioAnnotator.trigger', trigger, (bool,), False, True, False) + self.name = name + """An identifying name for this component.""" + self.title = title + """The audio annotator's title.""" + self.path = path + """The path to the audio file. Use mp3 or wav formats to achieve the best cross-browser support. See https://caniuse.com/?search=audio%20format for other formats.""" + self.tags = tags + """The master list of tags that can be used for annotations.""" + self.items = items + """Annotations to display on the image, if any.""" + self.trigger = trigger + """True if the form should be submitted as soon as an annotation is made.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + _guard_scalar('AudioAnnotator.name', self.name, (str,), True, False, False) + _guard_scalar('AudioAnnotator.title', self.title, (str,), False, False, False) + _guard_scalar('AudioAnnotator.path', self.path, (str,), False, False, False) + _guard_vector('AudioAnnotator.tags', self.tags, (AudioAnnotatorTag,), False, False, False) + _guard_vector('AudioAnnotator.items', self.items, (AudioAnnotatorItem,), False, True, False) + _guard_scalar('AudioAnnotator.trigger', self.trigger, (bool,), False, True, False) + return _dump( + name=self.name, + title=self.title, + path=self.path, + tags=[__e.dump() for __e in self.tags], + items=None if self.items is None else [__e.dump() for __e in self.items], + trigger=self.trigger, + ) + + @staticmethod + def load(__d: Dict) -> 'AudioAnnotator': + """Creates an instance of this class using the contents of a dict.""" + __d_name: Any = __d.get('name') + _guard_scalar('AudioAnnotator.name', __d_name, (str,), True, False, False) + __d_title: Any = __d.get('title') + _guard_scalar('AudioAnnotator.title', __d_title, (str,), False, False, False) + __d_path: Any = __d.get('path') + _guard_scalar('AudioAnnotator.path', __d_path, (str,), False, False, False) + __d_tags: Any = __d.get('tags') + _guard_vector('AudioAnnotator.tags', __d_tags, (dict,), False, False, False) + __d_items: Any = __d.get('items') + _guard_vector('AudioAnnotator.items', __d_items, (dict,), False, True, False) + __d_trigger: Any = __d.get('trigger') + _guard_scalar('AudioAnnotator.trigger', __d_trigger, (bool,), False, True, False) + name: str = __d_name + title: str = __d_title + path: str = __d_path + tags: List[AudioAnnotatorTag] = [AudioAnnotatorTag.load(__e) for __e in __d_tags] + items: Optional[List[AudioAnnotatorItem]] = None if __d_items is None else [AudioAnnotatorItem.load(__e) for __e in __d_items] + trigger: Optional[bool] = __d_trigger + return AudioAnnotator( + name, + title, + path, + tags, + items, + trigger, + ) + + class Facepile: """A face pile displays a list of personas. Each circle represents a person and contains their image or initials. Often this control is used when sharing who has access to a specific view or file. @@ -7233,6 +7412,7 @@ def __init__( persona: Optional[Persona] = None, text_annotator: Optional[TextAnnotator] = None, image_annotator: Optional[ImageAnnotator] = None, + audio_annotator: Optional[AudioAnnotator] = None, facepile: Optional[Facepile] = None, copyable_text: Optional[CopyableText] = None, menu: Optional[Menu] = None, @@ -7284,6 +7464,7 @@ def __init__( _guard_scalar('Component.persona', persona, (Persona,), False, True, False) _guard_scalar('Component.text_annotator', text_annotator, (TextAnnotator,), False, True, False) _guard_scalar('Component.image_annotator', image_annotator, (ImageAnnotator,), False, True, False) + _guard_scalar('Component.audio_annotator', audio_annotator, (AudioAnnotator,), False, True, False) _guard_scalar('Component.facepile', facepile, (Facepile,), False, True, False) _guard_scalar('Component.copyable_text', copyable_text, (CopyableText,), False, True, False) _guard_scalar('Component.menu', menu, (Menu,), False, True, False) @@ -7379,6 +7560,8 @@ def __init__( """Text annotator.""" self.image_annotator = image_annotator """Image annotator.""" + self.audio_annotator = audio_annotator + """Audio annotator.""" self.facepile = facepile """Facepile.""" self.copyable_text = copyable_text @@ -7437,6 +7620,7 @@ def dump(self) -> Dict: _guard_scalar('Component.persona', self.persona, (Persona,), False, True, False) _guard_scalar('Component.text_annotator', self.text_annotator, (TextAnnotator,), False, True, False) _guard_scalar('Component.image_annotator', self.image_annotator, (ImageAnnotator,), False, True, False) + _guard_scalar('Component.audio_annotator', self.audio_annotator, (AudioAnnotator,), False, True, False) _guard_scalar('Component.facepile', self.facepile, (Facepile,), False, True, False) _guard_scalar('Component.copyable_text', self.copyable_text, (CopyableText,), False, True, False) _guard_scalar('Component.menu', self.menu, (Menu,), False, True, False) @@ -7488,6 +7672,7 @@ def dump(self) -> Dict: persona=None if self.persona is None else self.persona.dump(), text_annotator=None if self.text_annotator is None else self.text_annotator.dump(), image_annotator=None if self.image_annotator is None else self.image_annotator.dump(), + audio_annotator=None if self.audio_annotator is None else self.audio_annotator.dump(), facepile=None if self.facepile is None else self.facepile.dump(), copyable_text=None if self.copyable_text is None else self.copyable_text.dump(), menu=None if self.menu is None else self.menu.dump(), @@ -7588,6 +7773,8 @@ def load(__d: Dict) -> 'Component': _guard_scalar('Component.text_annotator', __d_text_annotator, (dict,), False, True, False) __d_image_annotator: Any = __d.get('image_annotator') _guard_scalar('Component.image_annotator', __d_image_annotator, (dict,), False, True, False) + __d_audio_annotator: Any = __d.get('audio_annotator') + _guard_scalar('Component.audio_annotator', __d_audio_annotator, (dict,), False, True, False) __d_facepile: Any = __d.get('facepile') _guard_scalar('Component.facepile', __d_facepile, (dict,), False, True, False) __d_copyable_text: Any = __d.get('copyable_text') @@ -7643,6 +7830,7 @@ def load(__d: Dict) -> 'Component': persona: Optional[Persona] = None if __d_persona is None else Persona.load(__d_persona) text_annotator: Optional[TextAnnotator] = None if __d_text_annotator is None else TextAnnotator.load(__d_text_annotator) image_annotator: Optional[ImageAnnotator] = None if __d_image_annotator is None else ImageAnnotator.load(__d_image_annotator) + audio_annotator: Optional[AudioAnnotator] = None if __d_audio_annotator is None else AudioAnnotator.load(__d_audio_annotator) facepile: Optional[Facepile] = None if __d_facepile is None else Facepile.load(__d_facepile) copyable_text: Optional[CopyableText] = None if __d_copyable_text is None else CopyableText.load(__d_copyable_text) menu: Optional[Menu] = None if __d_menu is None else Menu.load(__d_menu) @@ -7694,6 +7882,7 @@ def load(__d: Dict) -> 'Component': persona, text_annotator, image_annotator, + audio_annotator, facepile, copyable_text, menu, diff --git a/py/h2o_wave/h2o_wave/ui.py b/py/h2o_wave/h2o_wave/ui.py index 6c3098190c1..39e08ed4dca 100644 --- a/py/h2o_wave/h2o_wave/ui.py +++ b/py/h2o_wave/h2o_wave/ui.py @@ -2497,7 +2497,7 @@ def image_annotator( trigger: True if the form should be submitted as soon as an annotation is drawn. image_height: The card’s image height. The actual image size is used by default. allowed_shapes: List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default. - events: The events to capture on this image annotator. One of `click` or `tool_change`. + events: The events to capture on this image annotator. One of `click` | `tool_change`. Returns: A `h2o_wave.types.ImageAnnotator` instance. """ @@ -2514,6 +2514,80 @@ def image_annotator( )) +def audio_annotator_tag( + name: str, + label: str, + color: str, +) -> AudioAnnotatorTag: + """Create a unique tag type for use in an audio annotator. + + Args: + name: An identifying name for this tag. + label: Text to be displayed for the annotation. + color: Hex or RGB color string to be used as the background color. + Returns: + A `h2o_wave.types.AudioAnnotatorTag` instance. + """ + return AudioAnnotatorTag( + name, + label, + color, + ) + + +def audio_annotator_item( + start: float, + end: float, + tag: str, +) -> AudioAnnotatorItem: + """Create an annotator item with initial selected tags or no tags. + + Args: + start: The start of the audio annotation in seconds. + end: The end of the audio annotation in seconds. + tag: The `name` of the audio annotator tag to refer to for the `label` and `color` of this item. + Returns: + A `h2o_wave.types.AudioAnnotatorItem` instance. + """ + return AudioAnnotatorItem( + start, + end, + tag, + ) + + +def audio_annotator( + name: str, + title: str, + path: str, + tags: List[AudioAnnotatorTag], + items: Optional[List[AudioAnnotatorItem]] = None, + trigger: Optional[bool] = None, +) -> Component: + """Create an audio annotator component. + + This component allows annotating and labeling parts of audio file. + + Args: + name: An identifying name for this component. + title: The audio annotator's title. + path: The path to the audio file. Use mp3 or wav formats to achieve the best cross-browser support. See https://caniuse.com/?search=audio%20format for other formats. + tags: The master list of tags that can be used for annotations. + items: Annotations to display on the image, if any. + trigger: True if the form should be submitted as soon as an annotation is made. + Returns: + A `h2o_wave.types.AudioAnnotator` instance. + """ + return Component(audio_annotator=AudioAnnotator( + name, + title, + path, + tags, + items, + trigger, + )) + + def facepile( items: List[Component], name: Optional[str] = None, diff --git a/r/R/ui.R b/r/R/ui.R index dbcc807b50a..71f1e856514 100644 --- a/r/R/ui.R +++ b/r/R/ui.R @@ -2894,7 +2894,7 @@ ui_image_annotator_item <- function( #' @param trigger True if the form should be submitted as soon as an annotation is drawn. #' @param image_height The card’s image height. The actual image size is used by default. #' @param allowed_shapes List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default. -#' @param events The events to capture on this image annotator. One of `click` or `tool_change`. +#' @param events The events to capture on this image annotator. One of `click` | `tool_change`. #' @return A ImageAnnotator instance. #' @export ui_image_annotator <- function( @@ -2930,6 +2930,86 @@ ui_image_annotator <- function( return(.o) } +#' Create a unique tag type for use in an audio annotator. +#' +#' @param name An identifying name for this tag. +#' @param label Text to be displayed for the annotation. +#' @param color Hex or RGB color string to be used as the background color. +#' @return A AudioAnnotatorTag instance. +#' @export +ui_audio_annotator_tag <- function( + name, + label, + color) { + .guard_scalar("name", "character", name) + .guard_scalar("label", "character", label) + .guard_scalar("color", "character", color) + .o <- list( + name=name, + label=label, + color=color) + class(.o) <- append(class(.o), c(.wave_obj, "WaveAudioAnnotatorTag")) + return(.o) +} + +#' Create an annotator item with initial selected tags or no tags. +#' +#' @param start The start of the audio annotation in seconds. +#' @param end The end of the audio annotation in seconds. +#' @param tag The `name` of the audio annotator tag to refer to for the `label` and `color` of this item. +#' @return A AudioAnnotatorItem instance. +#' @export +ui_audio_annotator_item <- function( + start, + end, + tag) { + .guard_scalar("start", "numeric", start) + .guard_scalar("end", "numeric", end) + .guard_scalar("tag", "character", tag) + .o <- list( + start=start, + end=end, + tag=tag) + class(.o) <- append(class(.o), c(.wave_obj, "WaveAudioAnnotatorItem")) + return(.o) +} + +#' Create an audio annotator component. +#' +#' This component allows annotating and labeling parts of audio file. +#' +#' @param name An identifying name for this component. +#' @param title The audio annotator's title. +#' @param path The path to the audio file. Use mp3 or wav formats to achieve the best cross-browser support. See https://caniuse.com/?search=audio%20format for other formats. +#' @param tags The master list of tags that can be used for annotations. +#' @param items Annotations to display on the image, if any. +#' @param trigger True if the form should be submitted as soon as an annotation is made. +#' @return A AudioAnnotator instance. +#' @export +ui_audio_annotator <- function( + name, + title, + path, + tags, + items = NULL, + trigger = NULL) { + .guard_scalar("name", "character", name) + .guard_scalar("title", "character", title) + .guard_scalar("path", "character", path) + .guard_vector("tags", "WaveAudioAnnotatorTag", tags) + .guard_vector("items", "WaveAudioAnnotatorItem", items) + .guard_scalar("trigger", "logical", trigger) + .o <- list(audio_annotator=list( + name=name, + title=title, + path=path, + tags=tags, + items=items, + trigger=trigger)) + class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent")) + return(.o) +} + #' A face pile displays a list of personas. Each circle represents a person and contains their image or initials. #' Often this control is used when sharing who has access to a specific view or file. #' diff --git a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml index 08176d9bfc4..34da03ce83e 100644 --- a/tools/intellij-plugin/src/main/resources/templates/wave-components.xml +++ b/tools/intellij-plugin/src/main/resources/templates/wave-components.xml @@ -6,6 +6,31 @@