Skip to content

Commit

Permalink
feat: Audio annotator. #1601 (#1706)
Browse files Browse the repository at this point in the history
  • Loading branch information
mturoci committed Aug 10, 2023
1 parent e715046 commit 5ab72b7
Show file tree
Hide file tree
Showing 25 changed files with 2,760 additions and 14 deletions.
Binary file added assets/examples/sample-audio.mp3
Binary file not shown.
35 changes: 35 additions & 0 deletions py/examples/audio_annotator.py
Original file line number Diff line number Diff line change
@@ -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()
Binary file added py/examples/audio_annotator_sample.mp3
Binary file not shown.
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
191 changes: 190 additions & 1 deletion py/h2o_lightwave/h2o_lightwave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -7694,6 +7882,7 @@ def load(__d: Dict) -> 'Component':
persona,
text_annotator,
image_annotator,
audio_annotator,
facepile,
copyable_text,
menu,
Expand Down
76 changes: 75 additions & 1 deletion py/h2o_lightwave/h2o_lightwave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 5ab72b7

Please sign in to comment.