diff --git a/porcupine.py b/porcupine.py index b36bcf5..9988aab 100644 --- a/porcupine.py +++ b/porcupine.py @@ -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