diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index ffb82e8..ca92b2a 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -3,9 +3,9 @@ name: Check Markdown links on: workflow_dispatch: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] jobs: markdown-link-check: diff --git a/.github/workflows/python-codestyle.yml b/.github/workflows/python-codestyle.yml index 8aa9848..e4116ee 100644 --- a/.github/workflows/python-codestyle.yml +++ b/.github/workflows/python-codestyle.yml @@ -3,11 +3,11 @@ name: Python Codestyle on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '**/*.py' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '**/*.py' diff --git a/.github/workflows/python-demos.yml b/.github/workflows/python-demos.yml index 5cb82bc..ec0087c 100644 --- a/.github/workflows/python-demos.yml +++ b/.github/workflows/python-demos.yml @@ -3,14 +3,14 @@ name: Python Demos on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python-demos.yml' - 'demo/python/**' - '!demo/python/README.md' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python-demos.yml' - 'demo/python/**' @@ -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/pvkoala-2.0.0-py3-none-any.whl + # ******************************************************** + - name: Install dependencies run: pip install -r requirements.txt @@ -61,6 +67,12 @@ jobs: steps: - uses: actions/checkout@v3 + # ************** REMOVE AFTER RELEASE ******************** + - name: Build binding + run: | + pip3 uninstall -y pvkoala && pip3 install wheel && cd ../../binding/python && python3 setup.py sdist bdist_wheel && pip3 install dist/pvkoala-2.0.0-py3-none-any.whl + # ******************************************************** + - name: Install dependencies run: pip3 install -r requirements.txt diff --git a/.github/workflows/python-perf.yml b/.github/workflows/python-perf.yml index 4a5d234..c3bba29 100644 --- a/.github/workflows/python-perf.yml +++ b/.github/workflows/python-perf.yml @@ -3,7 +3,7 @@ name: Python Performance on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python-perf.yml' - 'binding/python/__init__.py' @@ -18,7 +18,7 @@ on: - 'lib/windows/**' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python-perf.yml' - 'binding/python/__init__.py' diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 1cca42e..9d6098c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -3,7 +3,7 @@ name: Python on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python.yml' - 'binding/python/**' @@ -17,7 +17,7 @@ on: - 'resources/audio_samples/noise.wav' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python.yml' - 'binding/python/**' diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 55c1afc..df60996 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -3,9 +3,9 @@ name: SpellCheck on: workflow_dispatch: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] jobs: spellcheck: diff --git a/binding/python/_koala.py b/binding/python/_koala.py index cb50fdc..e3f9fe1 100644 --- a/binding/python/_koala.py +++ b/binding/python/_koala.py @@ -16,7 +16,27 @@ class KoalaError(Exception): - pass + 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 KoalaMemoryError(KoalaError): @@ -123,15 +143,34 @@ 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 = self.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 + init_func = library.pv_koala_init init_func.argtypes = [c_char_p, c_char_p, POINTER(POINTER(self.CKoala))] init_func.restype = self.PicovoiceStatuses self._handle = POINTER(self.CKoala)() - status = init_func(access_key.encode(), model_path.encode(), byref(self._handle)) + status = init_func( + access_key.encode(), + model_path.encode(), + byref(self._handle)) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Initialization failed', + message_stack=self._get_error_stack()) self._delete_func = library.pv_koala_delete self._delete_func.argtypes = [POINTER(self.CKoala)] @@ -144,7 +183,10 @@ def __init__( status = delay_sample_func(self._handle, delay_sample) if status is not self.PicovoiceStatuses.SUCCESS: self.delete() - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Failed to get delay samples', + message_stack=self._get_error_stack()) + self._delay_sample = delay_sample.value self._process_func = library.pv_koala_process @@ -193,7 +235,9 @@ def process(self, pcm: Sequence[int]) -> Sequence[int]: status = self._process_func(self._handle, pcm, enhanced_pcm) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Processing failed', + message_stack=self._get_error_stack()) # noinspection PyTypeChecker return list(enhanced_pcm) @@ -206,7 +250,9 @@ def reset(self) -> None: status = self._reset_func(self._handle) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Reset failed', + message_stack=self._get_error_stack()) def delete(self) -> None: """Releases resources acquired by Koala.""" @@ -239,6 +285,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 self.PicovoiceStatuses.SUCCESS: + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status](message='Unable to get Porcupine 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__ = [ 'Koala', diff --git a/binding/python/setup.py b/binding/python/setup.py index 480989e..9e5cbcd 100644 --- a/binding/python/setup.py +++ b/binding/python/setup.py @@ -32,7 +32,7 @@ shutil.copytree( os.path.join(os.path.dirname(__file__), '../../lib', platform), os.path.join(package_folder, 'lib', platform)) -manifest_in += "recursive-include pvkoala/lib/ *\n" +manifest_in += "recursive-include pvkoala/lib *\n" with open(os.path.join(os.path.dirname(__file__), 'MANIFEST.in'), 'w') as f: f.write(manifest_in) @@ -42,7 +42,7 @@ setuptools.setup( name="pvkoala", - version="1.0.1", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Koala Noise Suppression Engine.", diff --git a/binding/python/test_koala.py b/binding/python/test_koala.py index 4a11881..b27b082 100644 --- a/binding/python/test_koala.py +++ b/binding/python/test_koala.py @@ -17,7 +17,7 @@ import wave from typing import Optional, Sequence -from _koala import Koala +from _koala import Koala, KoalaError from _util import default_library_path, default_model_path @@ -111,6 +111,32 @@ def test_version(self) -> None: self.assertIsInstance(version, str) self.assertGreater(len(version), 0) + def test_message_stack(self): + relative_path = '../..' + + error = None + try: + k = Koala( + access_key='invalid', + model_path=default_model_path(relative_path), + library_path=default_library_path(relative_path)) + self.assertIsNone(k) + except KoalaError as e: + error = e.message_stack + + self.assertIsNotNone(error) + self.assertGreater(len(error), 0) + + try: + k = Koala( + access_key='invalid', + model_path=default_model_path(relative_path), + library_path=default_library_path(relative_path)) + self.assertIsNone(k) + except KoalaError as e: + self.assertEqual(len(error), len(e.message_stack)) + self.assertListEqual(list(error), list(e.message_stack)) + if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/demo/python/koala_demo_mic.py b/demo/python/koala_demo_mic.py index ad4157a..75b424a 100644 --- a/demo/python/koala_demo_mic.py +++ b/demo/python/koala_demo_mic.py @@ -13,7 +13,6 @@ import contextlib import math import struct -import sys import wave from pvkoala import create, KoalaActivationLimitError @@ -45,7 +44,7 @@ def main(): args = parser.parse_args() if args.show_audio_devices: - for index, name in enumerate(PvRecorder.get_audio_devices()): + for index, name in enumerate(PvRecorder.get_available_devices()): print('Device #%d: %s' % (index, name)) return diff --git a/demo/python/requirements.txt b/demo/python/requirements.txt index 5440243..e7508ed 100644 --- a/demo/python/requirements.txt +++ b/demo/python/requirements.txt @@ -1,2 +1,2 @@ -pvkoala==1.0.1 +pvkoala==2.0.0 pvrecorder==1.2.1 diff --git a/demo/python/setup.py b/demo/python/setup.py index 48ea6b4..71c482e 100644 --- a/demo/python/setup.py +++ b/demo/python/setup.py @@ -24,7 +24,7 @@ setuptools.setup( name="pvkoalademo", - version="1.0.3", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Koala Noise Suppression Engine demos", @@ -32,7 +32,7 @@ long_description_content_type="text/markdown", url="https://github.com/Picovoice/koala", packages=["pvkoalademo"], - install_requires=["pvkoala==1.0.1", "pvrecorder==1.2.1"], + install_requires=["pvkoala==2.0.0", "pvrecorder==1.2.1"], include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable",