Skip to content

Commit

Permalink
v2.0 python
Browse files Browse the repository at this point in the history
  • Loading branch information
ksyeo1010 committed Oct 31, 2023
1 parent 679c25f commit 09064cc
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 23 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/python-demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ jobs:
- name: Pre-build dependencies
run: python -m pip install --upgrade pip

# ************** REMOVE AFTER RELEASE ********************
- name: Build binding
run: |
pip install wheel && cd ../../binding/python && python setup.py sdist bdist_wheel && pip install dist/pveagle-0.2.0-py3-none-any.whl
# ********************************************************

- name: Install dependencies
run: pip install -r requirements.txt

Expand Down Expand Up @@ -71,6 +77,12 @@ jobs:
- name: Pre-build dependencies
run: python3 -m pip install --upgrade pip

# ************** REMOVE AFTER RELEASE ********************
- name: Build binding
run: |
pip install wheel && cd ../../binding/python && python setup.py sdist bdist_wheel && pip install dist/pveagle-0.2.0-py3-none-any.whl
# ********************************************************

- name: Install dependencies
run: pip install -r requirements.txt

Expand Down
103 changes: 92 additions & 11 deletions binding/python/_eagle.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,28 @@
from typing import Sequence, Tuple


class EagleError(Exception):
pass
class EagleError(Exception):
def __init__(self, message: str = '', message_stack: Sequence[str] = None):
super().__init__(message)

self._message = message
self._message_stack = list() if message_stack is None else message_stack

def __str__(self):
message = self._message
if len(self._message_stack) > 0:
message += ':'
for i in range(len(self._message_stack)):
message += '\n [%d] %s' % (i, self._message_stack[i])
return message

@property
def message(self) -> str:
return self._message

@property
def message_stack(self) -> Sequence[str]:
return self._message_stack


class EagleMemoryError(EagleError):
Expand Down Expand Up @@ -183,6 +203,20 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None:

library = cdll.LoadLibrary(library_path)

set_sdk_func = library.pv_set_sdk
set_sdk_func.argtypes = [c_char_p]
set_sdk_func.restype = None

set_sdk_func('python'.encode('utf-8'))

self._get_error_stack_func = library.pv_get_error_stack
self._get_error_stack_func.argtypes = [POINTER(POINTER(c_char_p)), POINTER(c_int)]
self._get_error_stack_func.restype = PicovoiceStatuses

self._free_error_stack_func = library.pv_free_error_stack
self._free_error_stack_func.argtypes = [POINTER(c_char_p)]
self._free_error_stack_func.restype = None

# noinspection PyArgumentList
self._eagle_profiler = POINTER(self.CEagleProfiler)()

Expand All @@ -198,7 +232,9 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None:
model_path.encode('utf-8'),
byref(self._eagle_profiler))
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Profile initialization failed',
message_stack=self._get_error_stack())

speaker_profile_size_func = library.pv_eagle_profiler_export_size
speaker_profile_size_func.argtypes = [
Expand All @@ -209,7 +245,9 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None:
profile_size = c_int32()
status = speaker_profile_size_func(self._eagle_profiler, byref(profile_size))
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Failed to get profile size',
message_stack=self._get_error_stack())
self._profile_size = profile_size.value

enroll_min_audio_length_sample_func = \
Expand All @@ -224,7 +262,9 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None:
self._eagle_profiler,
byref(min_enroll_samples))
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Failed to get min audio length sample',
message_stack=self._get_error_stack())
self._min_enroll_samples = min_enroll_samples.value

self._delete_func = library.pv_eagle_profiler_delete
Expand Down Expand Up @@ -298,7 +338,9 @@ def enroll(self, pcm: Sequence[int]) -> Tuple[float, EagleProfilerEnrollFeedback
byref(percentage))
feedback = EagleProfilerEnrollFeedback(feedback_code.value)
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Enrollment failed',
message_stack=self._get_error_stack())

return percentage.value, feedback

Expand All @@ -316,7 +358,9 @@ def export(self) -> EagleProfile:
byref(profile)
)
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Export failed',
message_stack=self._get_error_stack())

return EagleProfile(cast(profile, c_void_p), self._profile_size)

Expand All @@ -328,7 +372,9 @@ def reset(self) -> None:

status = self._reset_func(self._eagle_profiler)
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Profile reset failed',
message_stack=self._get_error_stack())

def delete(self) -> None:
"""
Expand Down Expand Up @@ -400,6 +446,20 @@ def __init__(

library = cdll.LoadLibrary(library_path)

set_sdk_func = library.pv_set_sdk
set_sdk_func.argtypes = [c_char_p]
set_sdk_func.restype = None

set_sdk_func('python'.encode('utf-8'))

self._get_error_stack_func = library.pv_get_error_stack
self._get_error_stack_func.argtypes = [POINTER(POINTER(c_char_p)), POINTER(c_int)]
self._get_error_stack_func.restype = PicovoiceStatuses

self._free_error_stack_func = library.pv_free_error_stack
self._free_error_stack_func.argtypes = [POINTER(c_char_p)]
self._free_error_stack_func.restype = None

# noinspection PyArgumentList
self._eagle = POINTER(self.CEagle)()

Expand All @@ -423,7 +483,9 @@ def __init__(
profile_bytes,
byref(self._eagle))
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Initialization failed',
message_stack=self._get_error_stack())

self._delete_func = library.pv_eagle_delete
self._delete_func.argtypes = [POINTER(self.CEagle)]
Expand Down Expand Up @@ -471,7 +533,9 @@ def process(self, pcm: Sequence[int]) -> Sequence[float]:

status = self._process_func(self._eagle, pcm, self._scores)
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Process failed',
message_stack=self._get_error_stack())

# noinspection PyTypeChecker
return [float(score) for score in self._scores]
Expand All @@ -485,7 +549,9 @@ def reset(self) -> None:

status = self._reset_func(self._eagle)
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status]()
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](
message='Reset failed',
message_stack=self._get_error_stack())

def delete(self) -> None:
"""
Expand Down Expand Up @@ -518,6 +584,21 @@ def version(self) -> str:

return self._version

def _get_error_stack(self) -> Sequence[str]:
message_stack_ref = POINTER(c_char_p)()
message_stack_depth = c_int()
status = self._get_error_stack_func(byref(message_stack_ref), byref(message_stack_depth))
if status is not PicovoiceStatuses.SUCCESS:
raise _PICOVOICE_STATUS_TO_EXCEPTION[status](message='Unable to get Eagle error state')

message_stack = list()
for i in range(message_stack_depth.value):
message_stack.append(message_stack_ref[i].decode('utf-8'))

self._free_error_stack_func(message_stack_ref)

return message_stack


__all__ = [
'Eagle',
Expand Down
2 changes: 1 addition & 1 deletion binding/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

setuptools.setup(
name="pveagle",
version="0.1.0",
version="0.2.0",
author="Picovoice",
author_email="[email protected]",
description="Eagle Speaker Recognition Engine",
Expand Down
30 changes: 30 additions & 0 deletions binding/python/test_eagle.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from _eagle import (
Eagle,
EagleError,
EagleProfiler,
EagleProfilerEnrollFeedback
)
Expand Down Expand Up @@ -120,6 +121,35 @@ def test_version(self) -> None:
def test_frame_length(self) -> None:
self.assertGreater(self.eagle.frame_length, 0)

def test_message_stack(self):
relative_path = '../..'
profile = self.eagle_profiler.export()

error = None
try:
eagle = Eagle(
access_key='invalid',
model_path=default_model_path(relative_path),
library_path=default_library_path(relative_path),
speaker_profiles=[profile])
self.assertIsNone(eagle)
except EagleError as e:
error = e.message_stack

self.assertIsNotNone(error)
self.assertGreater(len(error), 0)

try:
eagle = Eagle(
access_key='invalid',
model_path=default_model_path(relative_path),
library_path=default_library_path(relative_path),
speaker_profiles=[profile])
self.assertIsNone(eagle)
except EagleError as e:
self.assertEqual(len(error), len(e.message_stack))
self.assertListEqual(list(error), list(e.message_stack))


if __name__ == '__main__':
parser = argparse.ArgumentParser()
Expand Down
4 changes: 0 additions & 4 deletions demo/python/eagle_demo_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ def main():
access_key=args.access_key,
model_path=args.model_path,
library_path=args.library_path)
except pveagle.EagleInvalidArgumentError:
print("One or more arguments provided to Eagle is invalid: ", args)
print("If all other arguments seem valid, ensure that '%s' is a valid AccessKey" % args.access_key)
raise
except pveagle.EagleError as e:
print("Failed to initialize EagleProfiler: ", e)
raise
Expand Down
4 changes: 0 additions & 4 deletions demo/python/eagle_demo_mic.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,6 @@ def main():
access_key=args.access_key,
model_path=args.model_path,
library_path=args.library_path)
except pveagle.EagleInvalidArgumentError:
print("One or more arguments provided to Eagle is invalid: ", args)
print("If all other arguments seem valid, ensure that '%s' is a valid AccessKey" % args.access_key)
raise
except pveagle.EagleError as e:
print("Failed to initialize Eagle: %s" % e)
raise
Expand Down
2 changes: 1 addition & 1 deletion demo/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pveagle==0.1.0
pveagle==0.2.0
pvrecorder==1.2.1
4 changes: 2 additions & 2 deletions demo/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@

setuptools.setup(
name="pveagledemo",
version="0.1.2",
version="0.2.0",
author="Picovoice",
author_email="[email protected]",
description="Eagle Speaker Recognition Engine demos",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/Picovoice/eagle",
packages=["pveagledemo"],
install_requires=["pveagle==0.1.0", "pvrecorder==1.2.1"],
install_requires=["pveagle==0.2.0", "pvrecorder==1.2.1"],
include_package_data=True,
classifiers=[
"Development Status :: 4 - Beta",
Expand Down

0 comments on commit 09064cc

Please sign in to comment.