import logging
from pathlib import Path
from typing import Optional, Iterator, Dict
import pocketsphinx
from sttts.api.message import Message, SttMessageType, SttKeywordMap
from sttts.api.model import RecognizerModel, Recognizer
class SphinxRecognizer(RecognizerModel, Recognizer):
"""
Use the `Python bindings <https://pocketsphinx.readthedocs.io/en/latest/pocketsphinx.html>`__ of the
`PocketSphinx <https://pypi.org/project/pocketsphinx/>`__ speech recognizer package.
Direct :class:`pocketsphinx.Decoder` access is possible without the oversimplified wrappers for audio or live
speech, as the :class:`.speech.sphinx.SphinxSegmenter` implements a :class:`pocketsphinx.Endpointer` beforehand.
Generic transcription capabilities seem to be rather poor for nowadays standards, making it more suitable for
specific speech detection on a limited dictionary.
More recent models than the ``en-us`` one that the package ships with might be available on the
`PocketSphinx <https://github.com/cmusphinx/pocketsphinx>`__ project page or from
`SpeechRecognition <https://github.com/Uberi/speech_recognition>`__.
"""
def __init__(self, keywords: SttKeywordMap, *,
model_path: Optional[str] = None,
hmm: Optional[str] = None, lm: Optional[str] = None, dct: Optional[str] = None,
**kwargs) -> None:
"""
:param str model_path: Common base path for the ``hmm``, ``lm``, and ``dct`` arguments.
Default to use the ``en-us`` model that ships with :mod:`pocketsphinx`.
:param str hmm: Sub-path to the directory containing acoustic model files, such as ``acoustic-model``.
:param str lm: Sub-path to the N-Gram language model, such as ``language-model.lm.bin``.
:param str dct: Sub-path to the pronunciation dictionary, such as ``pronounciation-dictionary.dict``.
:param kwargs: Extra :class:`pocketsphinx.Config`
`parameters <https://pocketsphinx.readthedocs.io/en/latest/config_params.html>`__
passed to :class:`pocketsphinx.Decoder`.
"""
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
pocketsphinx.set_loglevel("DEBUG" if self._logger.getEffectiveLevel() == logging.DEBUG else "WARN")
base_path: Path = Path(model_path if model_path is not None else pocketsphinx.get_model_path())
model_args: Dict = {
"hmm": str(base_path / hmm),
"lm": str(base_path / lm),
"dict": str(base_path / dct)
} if hmm and lm and dct else {}
self._sample_rate: int = 16000
self._recognizer: pocketsphinx.Decoder = pocketsphinx.Decoder(pocketsphinx.Config(**kwargs),
loglevel="INFO",
samprate=self._sample_rate,
**model_args)
self._recording: bool = False
self._keywords: SttKeywordMap = keywords
def sample_rate(self) -> int:
return self._sample_rate
def accept(self, buffer: bytes) -> Iterator[Message[SttMessageType]]:
# NB: already segmented, no need for incremental by get_in_speech, which is part of the sphinx speech segmenter
self._recognizer.start_utt()
self._recognizer.process_raw(buffer, full_utt=True)
self._recognizer.end_utt()
result: str = self._recognizer.hyp().hypstr
yield from self._keywords.map_utterance(result)
def __enter__(self) -> Recognizer:
self._logger.info(f"Entering {str(self._recognizer)}")
return self
def __exit__(self, *args) -> None:
del self._recognizer