Bump Porcupine Python wrapper

This commit is contained in:
Uli
2020-02-16 18:51:01 +01:00
parent fc68d04f29
commit 21a2a8f9b4
+61 -48
View File
@@ -1,17 +1,12 @@
#
# Copyright 2018 Picovoice Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
# file accompanying this source.
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
import os
@@ -20,7 +15,7 @@ from enum import Enum
class Porcupine(object):
"""Python binding for Picovoice's wake word detection (aka Porcupine) library."""
"""Python binding for Picovoice's wake word detection (Porcupine) engine."""
class PicovoiceStatuses(Enum):
"""Status codes corresponding to 'pv_status_t' defined in 'include/picovoice.h'"""
@@ -29,11 +24,17 @@ class Porcupine(object):
OUT_OF_MEMORY = 1
IO_ERROR = 2
INVALID_ARGUMENT = 3
STOP_ITERATION = 4
KEY_ERROR = 5
INVALID_STATE = 6
_PICOVOICE_STATUS_TO_EXCEPTION = {
PicovoiceStatuses.OUT_OF_MEMORY: MemoryError,
PicovoiceStatuses.IO_ERROR: IOError,
PicovoiceStatuses.INVALID_ARGUMENT: ValueError
PicovoiceStatuses.INVALID_ARGUMENT: ValueError,
PicovoiceStatuses.STOP_ITERATION: StopIteration,
PicovoiceStatuses.KEY_ERROR: KeyError,
PicovoiceStatuses.INVALID_STATE: ValueError,
}
class CPorcupine(Structure):
@@ -48,9 +49,9 @@ class Porcupine(object):
keyword_file_paths=None,
sensitivities=None):
"""
Loads Porcupine's shared library and creates an instance of wake word detection object.
Constructor.
:param library_path: Absolute path to Porcupine's shared library.
:param library_path: Absolute path to Porcupine's dynamic library.
:param model_file_path: Absolute path to file containing model parameters.
:param keyword_file_path: Absolute path to keyword file containing hyper-parameters. If not present then
'keyword_file_paths' will be used.
@@ -64,38 +65,38 @@ class Porcupine(object):
"""
if not os.path.exists(library_path):
raise IOError(f"Could not find Porcupine's library at '{library_path}'")
raise IOError("could'nt find Porcupine's library at '%s'" % library_path)
library = cdll.LoadLibrary(library_path)
if not os.path.exists(model_file_path):
raise IOError(f"Could not find model file at '{model_file_path}'")
raise IOError("could'nt find model file at '%s'" % model_file_path)
if sensitivity is not None and keyword_file_path is not None:
if not os.path.exists(keyword_file_path):
raise IOError(f"Could not find keyword file at '{keyword_file_path}'")
raise IOError("could'nt' find keyword file at '%s'" % keyword_file_path)
keyword_file_paths = [keyword_file_path]
if not (0 <= sensitivity <= 1):
raise ValueError('Sensitivity should be within [0, 1]')
raise ValueError('sensitivity should be within [0, 1]')
sensitivities = [sensitivity]
elif sensitivities is not None and keyword_file_paths is not None:
if len(keyword_file_paths) != len(sensitivities):
raise ValueError("Different number of sensitivity and keyword file path parameters are provided.")
raise ValueError("different number of sensitivity and keyword file path parameters are provided.")
for x in keyword_file_paths:
if not os.path.exists(os.path.expanduser(x)):
raise IOError(f"Could not find keyword file at '{x}'")
raise IOError("could not find keyword file at '%s'" % x)
for x in sensitivities:
if not (0 <= x <= 1):
raise ValueError('Sensitivity should be within [0, 1]')
raise ValueError('sensitivity should be within [0, 1]')
else:
raise ValueError("Sensitivity and/or keyword file path is missing")
raise ValueError("sensitivity and/or keyword file path is missing")
self._num_keywords = len(keyword_file_paths)
init_func = library.pv_porcupine_multiple_keywords_init
init_func = library.pv_porcupine_init
init_func.argtypes = [
c_char_p,
c_int,
@@ -107,44 +108,43 @@ class Porcupine(object):
self._handle = POINTER(self.CPorcupine)()
status = init_func(
model_file_path.encode(),
model_file_path.encode('utf-8'),
self._num_keywords,
(c_char_p * self._num_keywords)(*[os.path.expanduser(x).encode() for x in keyword_file_paths]),
(c_char_p * self._num_keywords)(*[os.path.expanduser(x).encode('utf-8') for x in keyword_file_paths]),
(c_float * self._num_keywords)(*sensitivities),
byref(self._handle))
if status is not self.PicovoiceStatuses.SUCCESS:
raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]('Initialization failed')
self.process_func = library.pv_porcupine_multiple_keywords_process
self.process_func.argtypes = [POINTER(self.CPorcupine), POINTER(c_short), POINTER(c_int)]
self.process_func.restype = self.PicovoiceStatuses
raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]('initialization failed')
self._delete_func = library.pv_porcupine_delete
self._delete_func.argtypes = [POINTER(self.CPorcupine)]
self._delete_func.restype = None
self._sample_rate = library.pv_sample_rate()
self.process_func = library.pv_porcupine_process
self.process_func.argtypes = [POINTER(self.CPorcupine), POINTER(c_short), POINTER(c_int)]
self.process_func.restype = self.PicovoiceStatuses
version_func = library.pv_porcupine_version
version_func.argtypes = []
version_func.restype = c_char_p
self._version = version_func().decode('utf-8')
self._frame_length = library.pv_porcupine_frame_length()
@property
def sample_rate(self):
"""Audio sample rate accepted by Porcupine library."""
self._sample_rate = library.pv_sample_rate()
return self._sample_rate
def delete(self):
"""Releases resources acquired by Porcupine's library."""
@property
def frame_length(self):
"""Number of audio samples per frame expected by C library."""
return self._frame_length
self._delete_func(self._handle)
def process(self, pcm):
"""
Monitors incoming audio stream for given wake word(s).
Processes a frame of the incoming audio stream and emits the detection result.
:param pcm: An array (or array-like) of consecutive audio samples. For more information regarding required audio
properties (i.e. sample rate, number of channels encoding, and number of samples per frame) please refer to
'include/pv_porcupine.h'.
:param pcm: A frame of audio samples. The number of samples per frame can be attained by calling
'.frame_length'. The incoming audio needs to have a sample rate equal to '.sample_rate' and be 16-bit
linearly-encoded. Porcupine operates on single-channel audio.
:return: For a single wake-word use cse True if wake word is detected. For multiple wake-word use case it
returns the index of detected wake-word. Indexing is 0-based and according to ordering of input keyword file
paths. It returns -1 when no keyword is detected.
@@ -153,7 +153,7 @@ class Porcupine(object):
result = c_int()
status = self.process_func(self._handle, (c_short * len(pcm))(*pcm), byref(result))
if status is not self.PicovoiceStatuses.SUCCESS:
raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]('Processing failed')
raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]()
keyword_index = result.value
@@ -162,7 +162,20 @@ class Porcupine(object):
else:
return keyword_index
def delete(self):
"""Releases resources acquired by Porcupine's library."""
@property
def version(self):
"""Getter for version"""
self._delete_func(self._handle)
return self._version
@property
def frame_length(self):
"""Getter for number of audio samples per frame."""
return self._frame_length
@property
def sample_rate(self):
"""Audio sample rate accepted by Picovoice."""
return self._sample_rate